/* webim javascript SDK
 * VER 1.7.0
 */

/* webim API definitions
 */
var msgCache = {};
var webim = { // namespace object webim

  /* function init
   *   sdk登录
   * params:
   *   loginInfo      - Object, 登录身份相关参数集合，详见下面
   *   {
   *     sdkAppID     - String, 用户标识接入SDK的应用ID，必填
   *     accountType  - int, 账号类型，必填
   *     identifier   - String, 用户帐号,必须是字符串类型，必填
   *     identifierNick   - String, 用户昵称，选填
   *     userSig      - String, 鉴权Token，必须是字符串类型，必填
   *   }
   *   listeners      - Object, 事件回调函数集合, 详见下面
   *   {
   *     onConnNotify - function(connInfo), 用于收到连接状态相关通知的回调函数,目前未使用
   *     jsonpCallback -function(rspData),//IE9(含)以下浏览器用到的jsonp回调函数
   *     onRecvRoomTextMsg  - function(newMsgList), 用于收到消息通知的回调函数,
   *      newMsgList为新消息数组，格式为[Msg对象]
   *      使用方有两种处理回调: 1)处理newMsgList中的增量消息,2)直接访问webim.MsgStore获取最新的消息
   *     onGroupInfoChangeNotify  - function(groupInfo), 用于监听群组资料变更的回调函数,
   *          groupInfo为新的群组资料信息
   *     onGroupSystemNotifys - Object, 用于监听（多终端同步）群系统消息的回调函数对象
   *
   *   }
   *   options        - Object, 其它选项, 目前未使用
   * return:
   *   (无)
   */
  login: function (loginInfo, listeners, options) {
  },

  /* function syncMsgs
   *   拉取最新C2C消息
   *   一般不需要使用方直接调用, SDK底层会自动同步最新消息并通知使用方, 一种有用的调用场景是用户手动触发刷新消息
   * params:
   *   cbOk   - function(msgList)类型, 当同步消息成功时的回调函数, msgList为新消息数组，格式为[Msg对象],
   *            如果此参数为null或undefined则同步消息成功后会像自动同步那样回调cbNotify
   *   cbErr  - function(err)类型, 当同步消息失败时的回调函数, err为错误对象
   * return:
   *   (无)
   */
  syncMsgs: function (cbOk, cbErr) {
  },


  /* function getC2CHistoryMsgs
   * 拉取C2C漫游消息
   * params:
   *   options    - 请求参数
   *   cbOk   - function(msgList)类型, 成功时的回调函数, msgList为消息数组，格式为[Msg对象],
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getC2CHistoryMsgs: function (options, cbOk, cbErr) {
  },

  /* function syncGroupMsgs
   * 拉取群漫游消息
   * params:
   *   options    - 请求参数
   *   cbOk   - function(msgList)类型, 成功时的回调函数, msgList为消息数组，格式为[Msg对象],
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  syncGroupMsgs: function (options, cbOk, cbErr) {
  },

  /* function sendMsg
   *   发送一条消息
   * params:
   *   msg    - webim.Msg类型, 要发送的消息对象
   *   cbOk   - function()类型, 当发送消息成功时的回调函数
   *   cbErr  - function(err)类型, 当发送消息失败时的回调函数, err为错误对象
   * return:
   *   (无)
   */
  sendMsg: function (msg, cbOk, cbErr) {
  },

  /* function logout
   *   sdk登出
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  logout: function (cbOk, cbErr) {
  },

  /* function setAutoRead
   * 设置会话自动已读上报标志
   * params:
   *   selSess    - webim.Session类型, 当前会话
   *   isOn   - boolean, 将selSess的自动已读消息标志改为isOn，同时是否上报当前会话已读消息
   *   isResetAll - boolean，是否重置所有会话的自动已读标志
   * return:
   *   (无)
   */
  setAutoRead: function (selSess, isOn, isResetAll) {
  },

  /* function getProfilePortrait
   *   拉取资料（搜索用户）
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getProfilePortrait: function (options, cbOk, cbErr) {
  },

  /* function setProfilePortrait
   *   设置个人资料
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  setProfilePortrait: function (options, cbOk, cbErr) {
  },

  /* function applyAddFriend
   *   申请添加好友
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  applyAddFriend: function (options, cbOk, cbErr) {
  },

  /* function getPendency
   *   拉取好友申请
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getPendency: function (options, cbOk, cbErr) {
  },

  /* function deletePendency
   *   删除好友申请
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  deletePendency: function (options, cbOk, cbErr) {
  },

  /* function responseFriend
   *   响应好友申请
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  responseFriend: function (options, cbOk, cbErr) {
  },

  /* function getAllFriend
   *   拉取我的好友
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getAllFriend: function (options, cbOk, cbErr) {
  },

  /* function deleteFriend
   *   删除好友
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  deleteFriend: function (options, cbOk, cbErr) {
  },

  /* function addBlackList
   *   增加黑名单
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  addBlackList: function (options, cbOk, cbErr) {
  },

  /* function getBlackList
   *   删除黑名单
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getBlackList: function (options, cbOk, cbErr) {
  },

  /* function deleteBlackList
   *   我的黑名单
   * params:
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  deleteBlackList: function (options, cbOk, cbErr) {
  },

  /* function uploadPic
   *   上传图片
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  uploadPic: function (options, cbOk, cbErr) {
  },

  /* function createGroup
   *   创建群
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  createGroup: function (options, cbOk, cbErr) {
  },

  /* function applyJoinGroup
   *   申请加群
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  applyJoinGroup: function (options, cbOk, cbErr) {
  },

  /* function handleApplyJoinGroup
   *   处理申请加群(同意或拒绝)
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  handleApplyJoinGroup: function (options, cbOk, cbErr) {
  },

  /* function deleteApplyJoinGroupPendency
   *   删除加群申请
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  deleteApplyJoinGroupPendency: function (options, cbOk, cbErr) {
  },


  /* function quitGroup
   *  主动退群
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  quitGroup: function (options, cbOk, cbErr) {
  },

  /* function getGroupPublicInfo
   *   读取群公开资料-高级接口
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getGroupPublicInfo: function (options, cbOk, cbErr) {
  },

  /* function getGroupInfo
   *   读取群详细资料-高级接口
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getGroupInfo: function (options, cbOk, cbErr) {
  },

  /* function modifyGroupBaseInfo
   *   修改群基本资料
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  modifyGroupBaseInfo: function (options, cbOk, cbErr) {
  },

  /* function destroyGroup
   *  解散群
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  destroyGroup: function (options, cbOk, cbErr) {
  },

  /* function getJoinedGroupListHigh
   *   获取我的群组-高级接口
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getJoinedGroupListHigh: function (options, cbOk, cbErr) {
  },

  /* function getGroupMemberInfo
   *   获取群组成员列表
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  getGroupMemberInfo: function (options, cbOk, cbErr) {
  },

  /* function addGroupMember
   *   邀请好友加群
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  addGroupMember: function (options, cbOk, cbErr) {
  },

  /* function modifyGroupMember
   *   修改群成员资料（角色或者群消息提类型示）
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  modifyGroupMember: function (options, cbOk, cbErr) {
  },

  /* function forbidSendMsg
   *   设置群成员禁言时间
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  forbidSendMsg: function (options, cbOk, cbErr) {
  },

  /* function deleteGroupMember
   *   删除群成员
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  deleteGroupMember: function (options, cbOk, cbErr) {
  },

  /* function sendCustomGroupNotify
   *   发送自定义群通知
   * params:
   *   options    - 请求参数，详见api文档
   *   cbOk   - function()类型, 成功时回调函数
   *   cbErr  - function(err)类型, 失败时回调函数, err为错误对象
   * return:
   *   (无)
   */
  sendCustomGroupNotify: function (options, cbOk, cbErr) {
  },

  /* class webim.Msg
   *   一条消息的描述类, 消息发送、接收的API中都会涉及此类型的对象
   * properties:
   *   sess   - Session object-ref, 消息所属的会话(e.g:我与好友A的C2C会话，我与群组G的GROUP会话)
   *   isSend - Boolean, true表示是我发出消息, false表示是发给我的消息)
   *   seq    - Integer, 消息序列号, 用于判断消息是否同一条
   *   random - Integer, 消息随机数,用于判断消息是否同一条
   *   time   - Integer, 消息时间戳, 为unix timestamp
   *   fromAccount -String,  消息发送者帐号
   *   subType -Integer,  消息子类型，c2c消息时，0-表示普通消息；群消息时，0-普通消息，1-点赞消息，2-提示消息
   *   fromAccountNick -String,  消息发送者昵称
   *   elems  - Array of webim.Msg.Elem, 描述消息内容的元素列表
   * constructor:
   *   Msg(sess, isSend, seq,random time,fromAccount) - 构造函数, 参数定义同上面properties中定义
   * methods:
   *   addText(text)  - 向elems中添加一个TEXT元素
   *   addFace(face)  - 向elems中添加一个FACE元素
   *   toHtml()       - 转成可展示的html String
   *addFace
   * sub-class webim.Msg.Elem
   *   消息中一个组成元素的描述类, 一条消息的内容被抽象描述为N个元素的有序列表
   * properties:
   *   type   - 元素类型, 目前有TEXT(文本)、FACE(表情)、IMAGE(图片)等
   *   content- 元素内容体, 当TEXT时为String, 当PIC时为UrlString
   * constructor:
   *   Elem(type, content) - 构造函数, 参数定义同上面properties中定义
   *
   * sub-class webim.Msg.Elem.TextElem
   *   文本
   * properties:
   *   text  - String 内容
   * constructor:
   *   TextElem(text) - 构造函数, 参数定义同上面properties中定义
   *
   * sub-class webim.Msg.Elem.FaceElem
   *   表情
   * properties:
   *   index  - Integer 表情索引, 用户自定义
   *   data   - String 额外数据，用户自定义
   * constructor:
   *   FaceElem(index,data) - 构造函数, 参数定义同上面properties中定义
   *
   *
   */
  Msg: function (sess, isSend, seq, random, time, fromAccount, subType, fromAccountNick) {/*Class constructor*/
  },

  /* singleton object MsgStore
   * webim.MsgStore是消息数据的Model对象(参考MVC概念), 它提供接口访问当前存储的会话和消息数据
   * 下面说明下会话数据类型: Session
   *
   * class Session
   *   一个Session对象描述一个会话，会话可简单理解为最近会话列表的一个条目，它由两个字段唯一标识:
   *     type - String, 会话类型(如"C2C", "GROUP", ...)
   *     id   - String, 会话ID(如C2C类型中为对方帐号,"C2C"时为好友ID,"GROUP"时为群ID)
   * properties:
   *   (Session对象未对外暴露任何属性字段, 所有访问通过下面的getter方法进行)
   * methods:
   *   type()     - String, 返回会话类型,"C2C"表示与好友私聊，"GROUP"表示群聊
   *   id()       - String, 返回会话ID
   *   name()     - String, 返回会话标题(如C2C类型中为对方的昵称,暂不支持)
   *   icon()     - String, 返回会话图标(对C2C类型中为对方的头像URL，暂不支持)
   *   unread()           - Integer, 返回会话未读条数
   *   time()     - Integer, 返回会话最后活跃时间, 为unix timestamp
   *   curMaxMsgSeq() - Integer, 返回会话最大消息序列号
   *   msgCount() - Integer, 返回会话中所有消息条数
   *   msg(index) - webim.Msg, 返回会话中第index条消息
   */
  MsgStore: {
    /* function sessMap
     *   获取所有会话
     * return:
     *   所有会话对象
     */
    sessMap: function () {
      return {/*Object*/ };
    },
    /* function sessCount
     *   获取当前会话的个数
     * return:
     *   Integer, 会话个数
     */
    sessCount: function () {
      return 0;
    },

    /* function sessByTypeId
     *   根据会话类型和会话ID取得相应会话
     * params:
     *   type   - String, 会话类型(如"C2C", "GROUP", ...)
     *   id     - String, 会话ID(如对方ID)
     * return:
     *   Session, 会话对象(说明见上面)
     */
    sessByTypeId: function (type, id) {
      return {/*Session Object*/ };
    },
    /* function delSessByTypeId
     *   根据会话类型和会话ID删除相应会话
     * params:
     *   type   - String, 会话类型(如"C2C", "GROUP", ...)
     *   id     - String, 会话ID(如对方ID)
     * return:
     *   Boolean, 布尔类型
     */
    delSessByTypeId: function (type, id) {
      return true;
    },

    /* function resetCookieAndSyncFlag
     *   重置上一次读取新c2c消息Cookie和是否继续拉取标记
     * return:
     *
     */
    resetCookieAndSyncFlag: function () {
    },

    downloadMap: {}
  }

};

/* webim API implementation
 */
(function (webim) {
  //sdk版本
  var SDK = {
    'VERSION': '1.7.0',//sdk版本号
    'APPID': '537048168'//web im sdk 版本 APPID
  };

  //是否启用正式环境，默认启用
  var isAccessFormaEnvironment = true;
  // var isAccessFormaEnvironment = false;

  //后台接口主机
  var SRV_HOST = {
    'FORMAL': {
      'COMMON': 'https://webim.tim.qq.com',
      'PIC': 'https://pic.tim.qq.com'
    },
    'TEST': {
      'COMMON': 'https://test.tim.qq.com',
      'PIC': 'https://pic.tim.qq.com'
    }
  };

  //浏览器版本信息
  var BROWSER_INFO = {};
  //是否为ie9（含）以下
  var lowerBR = false;

  //服务名称
  var SRV_NAME = {
    'OPEN_IM': 'openim',//私聊（拉取未读c2c消息，长轮询，c2c消息已读上报等）服务名
    'GROUP': 'group_open_http_svc',//群组管理（拉取群消息，创建群，群成员管理，群消息已读上报等）服务名
    'FRIEND': 'sns',//关系链管理（好友管理，黑名单管理等）服务名
    'PROFILE': 'profile',//资料管理（查询，设置个人资料等）服务名
    'RECENT_CONTACT': 'recentcontact',//最近联系人服务名
    'PIC': 'openpic',//图片（或文件）服务名
    'BIG_GROUP': 'group_open_http_noauth_svc',//直播大群 群组管理（申请加大群）服务名
    'BIG_GROUP_LONG_POLLING': 'group_open_long_polling_http_noauth_svc',//直播大群 长轮询（拉取消息等）服务名
    'IM_OPEN_STAT': 'imopenstat'//质量上报，统计接口错误率
  };

  //不同服务对应的版本号
  var SRV_NAME_VER = {
    'openim': 'v4',
    'group_open_http_svc': 'v4',
    'sns': 'v4',
    'profile': 'v4',
    'recentcontact': 'v4',
    'openpic': 'v4',
    'group_open_http_noauth_svc': 'v1',
    'group_open_long_polling_http_noauth_svc': 'v1',
    'imopenstat': 'v4'
  };

  //不同的命令名对应的上报类型ID，用于接口质量上报
  var CMD_EVENT_ID_MAP = {
    'login': 1,//登录
    'pic_up': 3,//上传图片
    'apply_join_group': 9,//申请加入群组
    'create_group': 10,//创建群组
    'longpolling': 18,//普通长轮询
    'send_group_msg': 19,//群聊
    'sendmsg': 20//私聊
  };

  //聊天类型
  var SESSION_TYPE = {
    'C2C': 'C2C',//私聊
    'GROUP': 'GROUP'//群聊
  };

  //最近联系人类型
  var RECENT_CONTACT_TYPE = {
    'C2C': 1,//好友
    'GROUP': 2//群
  };

  //消息最大长度（字节）
  var MSG_MAX_LENGTH = {
    'C2C': 12000,//私聊消息
    'GROUP': 8898//群聊
  };

  //后台接口返回类型
  var ACTION_STATUS = {
    'OK': 'OK',//成功
    'FAIL': 'FAIL'//失败
  };

  var ERROR_CODE_CUSTOM = 99999;//自定义后台接口返回错误码

  //消息元素类型
  var MSG_ELEMENT_TYPE = {
    'TEXT': 'TIMTextElem',//文本
    'FACE': 'TIMFaceElem',//表情
    'IMAGE': 'TIMImageElem',//图片
    'CUSTOM': 'TIMCustomElem',//自定义
    'SOUND': 'TIMSoundElem',//语音,只支持显示
    'FILE': 'TIMFileElem',//文件,只支持显示
    'LOCATION': 'TIMLocationElem',//地理位置
    'GROUP_TIP': 'TIMGroupTipElem'//群提示消息,只支持显示
  };

  //图片类型
  var IMAGE_TYPE = {
    'ORIGIN': 1,//原图
    'LARGE': 2,//缩略大图
    'SMALL': 3//缩略小图
  };

  //上传资源包类型
  var UPLOAD_RES_PKG_FLAG = {
    'RAW_DATA': 0,//原始数据
    'BASE64_DATA': 1//base64编码数据
  };

  //下载文件配置
  var DOWNLOAD_FILE = {
    'BUSSINESS_ID': '10001',//下载文件业务ID
    'AUTH_KEY': '617574686b6579',//下载文件authkey
    'SERVER_IP': '182.140.186.147'//下载文件服务器IP
  };

  //下载文件类型
  var DOWNLOAD_FILE_TYPE = {
    "SOUND": 2106,//语音
    "FILE": 2107//普通文件
  };

  //上传资源类型
  var UPLOAD_RES_TYPE = {
    "IMAGE": 1,//图片
    "FILE": 2,//文件
    "SHORT_VIDEO": 3,//短视频
    "SOUND": 4//语音，PTT
  };

  //版本号，用于上传图片或文件接口
  var VERSION_INFO = {
    'APP_VERSION': '2.1',//应用版本号
    'SERVER_VERSION': 1//服务端版本号
  };

  //长轮询消息类型
  var LONG_POLLINNG_EVENT_TYPE = {
    "C2C": 1//新的c2c消息通知
    , "GROUP_COMMON": 3//新的群普通消息
    , "GROUP_TIP": 4//新的群提示消息
    , "GROUP_SYSTEM": 5//新的群系统消息
    , "GROUP_TIP2": 6//新的群提示消息2
    , "FRIEND_NOTICE": 7//好友系统通知
    , "PROFILE_NOTICE": 8//资料系统通知
    , "C2C_COMMON": 9//新的C2C消息
    , "C2C_EVENT": 10
  };

  //c2c消息子类型
  var C2C_MSG_SUB_TYPE = {
    "COMMON": 0//普通消息
  };
  //c2c消息子类型
  var C2C_EVENT_SUB_TYPE = {
    "READED": 92//已读消息同步
  };

  //群消息子类型
  var GROUP_MSG_SUB_TYPE = {
    "COMMON": 0,//普通消息
    "LOVEMSG": 1,//点赞消息
    "TIP": 2,//提示消息
    "REDPACKET": 3//红包消息
  };

  //群消息优先级类型
  var GROUP_MSG_PRIORITY_TYPE = {
    "REDPACKET": 1,//红包消息
    "COMMON": 2,//普通消息
    "LOVEMSG": 3//点赞消息
  };

  //群提示消息类型
  var GROUP_TIP_TYPE = {
    "JOIN": 1,//加入群组
    "QUIT": 2,//退出群组
    "KICK": 3,//被踢出群组
    "SET_ADMIN": 4,//被设置为管理员
    "CANCEL_ADMIN": 5,//被取消管理员
    "MODIFY_GROUP_INFO": 6,//修改群资料
    "MODIFY_MEMBER_INFO": 7//修改群成员信息
  };

  //群提示消息-群资料变更类型
  var GROUP_TIP_MODIFY_GROUP_INFO_TYPE = {
    "FACE_URL": 1,//修改群头像URL
    "NAME": 2,//修改群名称
    "OWNER": 3,//修改群主
    "NOTIFICATION": 4,//修改群公告
    "INTRODUCTION": 5//修改群简介
  };

  //群系统消息类型
  var GROUP_SYSTEM_TYPE = {
    "JOIN_GROUP_REQUEST": 1,//申请加群请求（只有管理员会收到）
    "JOIN_GROUP_ACCEPT": 2,//申请加群被同意（只有申请人能够收到）
    "JOIN_GROUP_REFUSE": 3,//申请加群被拒绝（只有申请人能够收到）
    "KICK": 4,//被管理员踢出群(只有被踢者接收到)
    "DESTORY": 5,//群被解散(全员接收)
    "CREATE": 6,//创建群(创建者接收, 不展示)
    "INVITED_JOIN_GROUP_REQUEST": 7,//邀请加群(被邀请者接收)
    "QUIT": 8,//主动退群(主动退出者接收, 不展示)
    "SET_ADMIN": 9,//设置管理员(被设置者接收)
    "CANCEL_ADMIN": 10,//取消管理员(被取消者接收)
    "REVOKE": 11,//群已被回收(全员接收, 不展示)
    "READED": 15,//群已被回收(全员接收, 不展示)
    "CUSTOM": 255//用户自定义通知(默认全员接收)
  };

  //好友系统通知子类型
  var FRIEND_NOTICE_TYPE = {
    "FRIEND_ADD": 1,//好友表增加
    "FRIEND_DELETE": 2,//好友表删除
    "PENDENCY_ADD": 3,//未决增加
    "PENDENCY_DELETE": 4,//未决删除
    "BLACK_LIST_ADD": 5,//黑名单增加
    "BLACK_LIST_DELETE": 6,//黑名单删除
    "PENDENCY_REPORT": 7,//未决已读上报
    "FRIEND_UPDATE": 8//好友数据更新
  };

  //资料系统通知子类型
  var PROFILE_NOTICE_TYPE = {
    "PROFILE_MODIFY": 1//资料修改
  };

  //腾讯登录服务错误码（用于托管模式）
  var TLS_ERROR_CODE = {
    'OK': 0,//成功
    'SIGNATURE_EXPIRATION': 11//用户身份凭证过期
  };

  //长轮询连接状态
  var CONNECTION_STATUS = {
    'INIT': -1,//初始化
    'ON': 0,//连接正常
    'RECONNECT': 1,//连接恢复正常
    'OFF': 9999//连接已断开,可能是用户网络问题，或者长轮询接口报错引起的
  };

  var UPLOAD_PIC_BUSSINESS_TYPE = {//图片业务类型
    'GROUP_MSG': 1,//私聊图片
    'C2C_MSG': 2,//群聊图片
    'USER_HEAD': 3,//用户头像
    'GROUP_HEAD': 4//群头像
  };

  var FRIEND_WRITE_MSG_ACTION = {//好友输入消息状态
    'ING': 14,//正在输入
    'STOP': 15//停止输入
  };

  //ajax默认超时时间，单位：毫秒
  var ajaxDefaultTimeOut = 15000;

  //大群长轮询接口返回正常时，延时一定时间再发起下一次请求
  var OK_DELAY_TIME = 1000;

  //大群长轮询接口发生错误时，延时一定时间再发起下一次请求
  var ERROR_DELAY_TIME = 5000;

  //群提示消息最多显示人数
  var GROUP_TIP_MAX_USER_COUNT = 10;

  //长轮询连接状态
  var curLongPollingStatus = CONNECTION_STATUS.INIT;

  //当长轮询连接断开后，是否已经回调过
  var longPollingOffCallbackFlag = false;

  //当前长轮询返回错误次数
  var curLongPollingRetErrorCount = 0;

  //长轮询默认超时时间，单位：毫秒
  var longPollingDefaultTimeOut = 60000;

  //长轮询返回错误次数达到一定值后，发起新的长轮询请求间隔时间，单位：毫秒
  var longPollingIntervalTime = 5000;

  //没有新消息时，长轮询返回60008错误码是正常的
  var longPollingTimeOutErrorCode = 60008;

  //多实例登录被kick的错误码
  var longPollingKickedErrorCode = 91101;

  var LongPollingId = null;

  //当前大群长轮询返回错误次数
  var curBigGroupLongPollingRetErrorCount = 0;

  //最大允许长轮询返回错误次数
  var LONG_POLLING_MAX_RET_ERROR_COUNT = 10;

  //上传重试累计
  var Upload_Retry_Times = 0;
  //最大上传重试
  var Upload_Retry_Max_Times = 20;

  //ie7/8/9采用jsonp方法解决ajax跨域限制
  var jsonpRequestId = 0;//jsonp请求id
  //最新jsonp请求返回的json数据
  var jsonpLastRspData = null;
  //兼容ie7/8/9,jsonp回调函数
  var jsonpCallback = null;

  var uploadResultIframeId = 0;//用于上传图片的iframe id

  var ipList = [];//文件下载地址
  var authkey = null;//文件下载票据
  var expireTime = null;//文件下载票据超时时间

  //错误码
  var ERROR = {};
  //当前登录用户
  var ctx = {
    sdkAppID: null,
    appIDAt3rd: null,
    accountType: null,
    identifier: null,
    tinyid: null,
    identifierNick: null,
    userSig: null,
    a2: null,
    contentType: 'json',
    apn: 1
  };
  var opt = {};
  var xmlHttpObjSeq = 0;//ajax请求id
  var xmlHttpObjMap = {};//发起的ajax请求
  var curSeq = 0;//消息seq
  var tempC2CMsgList = [];//新c2c消息临时缓存
  var tempC2CHistoryMsgList = [];//漫游c2c消息临时缓存

  var maxApiReportItemCount = 20;//一次最多上报条数
  var apiReportItems = [];//暂存api接口质量上报数据

  var Resources = {
    downloadMap: {}
  };

  //表情标识字符和索引映射关系对象，用户可以自定义
  var emotionDataIndexs = {
    "[惊讶]": 0,
    "[撇嘴]": 1,
    "[色]": 2,
    "[发呆]": 3,
    "[得意]": 4,
    "[流泪]": 5,
    "[害羞]": 6,
    "[闭嘴]": 7,
    "[睡]": 8,
    "[大哭]": 9,
    "[尴尬]": 10,
    "[发怒]": 11,
    "[调皮]": 12,
    "[龇牙]": 13,
    "[微笑]": 14,
    "[难过]": 15,
    "[酷]": 16,
    "[冷汗]": 17,
    "[抓狂]": 18,
    "[吐]": 19,
    "[偷笑]": 20,
    "[可爱]": 21,
    "[白眼]": 22,
    "[傲慢]": 23,
    "[饿]": 24,
    "[困]": 25,
    "[惊恐]": 26,
    "[流汗]": 27,
    "[憨笑]": 28,
    "[大兵]": 29,
    "[奋斗]": 30,
    "[咒骂]": 31,
    "[疑问]": 32,
    "[嘘]": 33,
    "[晕]": 34
  };

  //表情对象，用户可以自定义
  var emotions = {};
  //工具类
  var tool = new function () {

    //格式化时间戳
    //format格式如下：
    //yyyy-MM-dd hh:mm:ss 年月日时分秒(默认格式)
    //yyyy-MM-dd 年月日
    //hh:mm:ss 时分秒
    this.formatTimeStamp = function (timestamp, format) {
      if (!timestamp) {
        return 0;
      }
      var formatTime;
      format = format || 'yyyy-MM-dd hh:mm:ss';
      var date = new Date(timestamp * 1000);
      var o = {
        "M+": date.getMonth() + 1, //月份
        "d+": date.getDate(), //日
        "h+": date.getHours(), //小时
        "m+": date.getMinutes(), //分
        "s+": date.getSeconds() //秒
      };
      if (/(y+)/.test(format)) {
        formatTime = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
      } else {
        formatTime = format;
      }
      for (var k in o) {
        if (new RegExp("(" + k + ")").test(formatTime))
          formatTime = formatTime.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
      }
      return formatTime;
    };

    //根据群类型英文名转换成中文名
    this.groupTypeEn2Ch = function (type_en) {
      var type_ch = null;
      switch (type_en) {
        case 'Public':
          type_ch = '公开群';
          break;
        case 'ChatRoom':
          type_ch = '聊天室';
          break;
        case 'Private':
          type_ch = '讨论组';
          break;
        case 'AVChatRoom':
          type_ch = '直播聊天室';
          break;
        default:
          type_ch = type_en;
          break;
      }
      return type_ch;
    };
    //根据群类型中文名转换成英文名
    this.groupTypeCh2En = function (type_ch) {
      var type_en = null;
      switch (type_ch) {
        case '公开群':
          type_en = 'Public';
          break;
        case '聊天室':
          type_en = 'ChatRoom';
          break;
        case '讨论组':
          type_en = 'Private';
          break;
        case '直播聊天室':
          type_en = 'AVChatRoom';
          break;
        default:
          type_en = type_ch;
          break;
      }
      return type_en;
    };
    //根据群身份英文名转换成群身份中文名
    this.groupRoleEn2Ch = function (role_en) {
      var role_ch = null;
      switch (role_en) {
        case 'Member':
          role_ch = '成员';
          break;
        case 'Admin':
          role_ch = '管理员';
          break;
        case 'Owner':
          role_ch = '群主';
          break;
        default:
          role_ch = role_en;
          break;
      }
      return role_ch;
    };
    //根据群身份中文名转换成群身份英文名
    this.groupRoleCh2En = function (role_ch) {
      var role_en = null;
      switch (role_ch) {
        case '成员':
          role_en = 'Member';
          break;
        case '管理员':
          role_en = 'Admin';
          break;
        case '群主':
          role_en = 'Owner';
          break;
        default:
          role_en = role_ch;
          break;
      }
      return role_en;
    };
    //根据群消息提示类型英文转换中文
    this.groupMsgFlagEn2Ch = function (msg_flag_en) {
      var msg_flag_ch = null;
      switch (msg_flag_en) {
        case 'AcceptAndNotify':
          msg_flag_ch = '接收并提示';
          break;
        case 'AcceptNotNotify':
          msg_flag_ch = '接收不提示';
          break;
        case 'Discard':
          msg_flag_ch = '屏蔽';
          break;
        default:
          msg_flag_ch = msg_flag_en;
          break;
      }
      return msg_flag_ch;
    };
    //根据群消息提示类型中文名转换英文名
    this.groupMsgFlagCh2En = function (msg_flag_ch) {
      var msg_flag_en = null;
      switch (msg_flag_ch) {
        case '接收并提示':
          msg_flag_en = 'AcceptAndNotify';
          break;
        case '接收不提示':
          msg_flag_en = 'AcceptNotNotify';
          break;
        case '屏蔽':
          msg_flag_en = 'Discard';
          break;
        default:
          msg_flag_en = msg_flag_ch;
          break;
      }
      return msg_flag_en;
    };
    //将空格和换行符转换成HTML标签
    this.formatText2Html = function (text) {
      var html = text;
      if (html) {
        html = this.xssFilter(html);//用户昵称或群名称等字段会出现脚本字符串
        html = html.replace(/ /g, "&nbsp;");
        html = html.replace(/\n/g, "<br/>");
      }
      return html;
    };
    //将HTML标签转换成空格和换行符
    this.formatHtml2Text = function (html) {
      var text = html;
      if (text) {
        text = text.replace(/&nbsp;/g, " ");
        text = text.replace(/<br\/>/g, "\n");
      }
      return text;
    };
    //获取字符串(UTF-8编码)所占字节数
    //参考：http://zh.wikipedia.org/zh-cn/UTF-8
    this.getStrBytes = function (str) {
      if (str == null || str === undefined) return 0;
      if (typeof str != "string") {
        return 0;
      }
      var total = 0, charCode, i, len;
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0x007f) {
          total += 1;//字符代码在000000 – 00007F之间的，用一个字节编码
        } else if (charCode <= 0x07ff) {
          total += 2;//000080 – 0007FF之间的字符用两个字节
        } else if (charCode <= 0xffff) {
          total += 3;//000800 – 00D7FF 和 00E000 – 00FFFF之间的用三个字节，注: Unicode在范围 D800-DFFF 中不存在任何字符
        } else {
          total += 4;//010000 – 10FFFF之间的用4个字节
        }
      }
      return total;
    };


    //防止XSS攻击
    this.xssFilter = function (val) {
      val = val.toString();
      val = val.replace(/[<]/g, "&lt;");
      val = val.replace(/[>]/g, "&gt;");
      val = val.replace(/"/g, "&quot;");
      //val = val.replace(/'/g, "&#039;");
      return val;
    };

    //去掉头尾空白符
    this.trimStr = function (str) {
      if (!str) return '';
      str = str.toString();
      return str.replace(/(^\s*)|(\s*$)/g, "");
    };
    //判断是否为8位整数
    this.validNumber = function (str) {
      str = str.toString();
      return str.match(/(^\d{1,8}$)/g);
    };
    this.getReturnError = function (errorInfo, errorCode) {
      if (!errorCode) {
        errorCode = -100;
      }
      var error = {
        'ActionStatus': ACTION_STATUS.FAIL,
        'ErrorCode': errorCode,
        'ErrorInfo': errorInfo + "[" + errorCode + "]"
      };
      return error;
    };
    //设置cookie
    //name 名字
    //value 值
    //expires 有效期(单位：秒)
    //path
    //domain 作用域
    this.setCookie = function (name, value, expires, path, domain) {
      var exp = new Date();
      exp.setTime(exp.getTime() + expires * 1000);
      document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
    };
    //获取cookie
    this.getCookie = function (name) {
      var result = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
      if (result != null) {
        return unescape(result[2]);
      }
      return null;
    };
    //删除cookie
    this.delCookie = function (name) {
      var exp = new Date();
      exp.setTime(exp.getTime() - 1);
      var value = this.getCookie(name);
      if (value != null)
        document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
    };
    //判断浏览器版本
    this.getBrowserInfo = function () {
      return {
        'type': 'wechat app',
        'ver': -1
      };
    };

  };

  //日志对象
  var log = new function () {

    var on = true;

    this.setOn = function (onFlag) {
      on = onFlag;
    };

    this.getOn = function () {
      return on;
    };

    this.error = function (logStr) {
      try {
        on && console.error(logStr);
      } catch (e) {
      }
    };
    this.warn = function (logStr) {
      try {
        on && console.warn(logStr);
      } catch (e) {
      }
    };
    this.info = function (logStr) {
      try {
        on && console.info(logStr);
      } catch (e) {
      }
    };
    this.debug = function (logStr) {
      try {
        on && console.debug(logStr);
      } catch (e) {
      }
    };
  };
  //获取unix时间戳
  var unixtime = function (d) {
    if (!d) d = new Date();
    return Math.round(d.getTime() / 1000);
  };
  //时间戳转日期
  var fromunixtime = function (t) {
    return new Date(t * 1000);
  };
  //获取下一个消息序号
  var nextSeq = function () {
    if (curSeq) {
      curSeq = curSeq + 1;
    } else {
      curSeq = Math.round(Math.random() * 10000000);
    }
    return curSeq;
  };
  //产生随机数
  var createRandom = function () {
    return Math.round(Math.random() * 4294967296);
  };

  //获取ajax请求对象
  var getXmlHttp = function () {
    var xmlhttp = null;
    if (window.XMLHttpRequest) {
      xmlhttp = new XMLHttpRequest();
    } else {
      try {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
      } catch (e) {
        try {
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {
          return null;
        }
      }
    }
    return xmlhttp;
  }
  //发起ajax请求
  var ajaxRequest = function (meth, url, req, timeout, isLongPolling, cbOk, cbErr) {
    console.debug(url, req);
    wx.request({
      url: url,
      data: req,
      dataType: 'json',
      method: meth,
      header: {
        'Content-Type': 'application/json'
      },
      success: function (res) {
        curLongPollingRetErrorCount = curBigGroupLongPollingRetErrorCount = 0;
        if (cbOk) cbOk(res.data);
      },
      fail: function (res) {
        setTimeout(function () {
          var errInfo = "请求服务器失败,请检查你的网络是否正常";
          var error = tool.getReturnError(errInfo, -2);
          //if (!isLongPolling && cbErr) cbErr(error);
          if (cbErr) cbErr(error);
        }, 16);
      }
    });
  }
  //发起ajax请求（json格式数据）
  var ajaxRequestJson = function (meth, url, req, timeout, isLongPolling, cbOk, cbErr) {
    ajaxRequest(meth, url, JSON.stringify(req), timeout, isLongPolling, function (resp) {
      var json = null;
      //if (resp) eval('json=('+resp+');');//将返回的json字符串转换成json对象
      //if (resp) json=eval('('+resp+')');//将返回的json字符串转换成json对象
      if (resp) json = resp;//JSON.parse(resp);//将返回的json字符串转换成json对象
      if (cbOk) cbOk(json);
    }, cbErr);
  }
  //判断用户是否已登录
  var isLogin = function () {
    return ctx.sdkAppID && ctx.identifier;
  };
  //检查是否登录
  var checkLogin = function (cbErr, isNeedCallBack) {
    if (!isLogin()) {
      if (isNeedCallBack) {
        var errInfo = "请登录";
        var error = tool.getReturnError(errInfo, -4);

        if (cbErr) cbErr(error);
      }
      return false;
    }
    return true;
  };

  //检查是否访问正式环境
  var isAccessFormalEnv = function () {
    return isAccessFormaEnvironment;
  };

  //根据不同的服务名和命令，获取对应的接口地址
  var getApiUrl = function (srvName, cmd, cbOk, cbErr) {
    var srvHost = SRV_HOST;
    if (isAccessFormalEnv()) {
      srvHost = SRV_HOST.FORMAL.COMMON;
    } else {
      srvHost = SRV_HOST.TEST.COMMON;
    }

    //if (srvName == SRV_NAME.RECENT_CONTACT) {
    //    srvHost = SRV_HOST.TEST.COMMON;
    //}

    if (srvName == SRV_NAME.PIC) {
      if (isAccessFormalEnv()) {
        srvHost = SRV_HOST.FORMAL.PIC;
      } else {
        srvHost = SRV_HOST.TEST.PIC;
      }
    }

    var url = srvHost + '/' + SRV_NAME_VER[srvName] + '/' + srvName + '/' + cmd + '?websdkappid=' + SDK.APPID + "&v=" + SDK.VERSION;

    if (isLogin()) {
      if (cmd == 'login') {
        url += '&identifier=' + encodeURIComponent(ctx.identifier) + '&usersig=' + ctx.userSig;
      } else {
        if (ctx.tinyid && ctx.a2) {
          url += '&tinyid=' + ctx.tinyid + '&a2=' + ctx.a2;
        } else {
          if (cbErr) {
            log.error("tinyid或a2为空[" + srvName + "][" + cmd + "]");
            cbErr(tool.getReturnError("tinyid或a2为空[" + srvName + "][" + cmd + "]", -5));
            return false;
          }
        }
      }
      url += '&contenttype=' + ctx.contentType;
    }
    url += '&sdkappid=' + ctx.sdkAppID + '&accounttype=' + ctx.accountType + '&apn=' + ctx.apn + '&reqtime=' + unixtime();
    return url;
  };

  //获取语音下载url
  var getSoundDownUrl = function (uuid, senderId) {
    var soundUrl = null;
    if (authkey && ipList[0]) {
      soundUrl = "http://" + ipList[0] + "/asn.com/stddownload_common_file?authkey=" + authkey + "&bid=" + DOWNLOAD_FILE.BUSSINESS_ID + "&subbid=" + ctx.sdkAppID + "&fileid=" + uuid + "&filetype=" + DOWNLOAD_FILE_TYPE.SOUND + "&openid=" + senderId + "&ver=0";
    } else {
      log.error("拼接语音下载url不报错：ip或者authkey为空");
    }
    return soundUrl;
  };

  //获取文件下载地址
  var getFileDownUrl = function (uuid, senderId, fileName) {
    var fileUrl = null;
    if (authkey && ipList[0]) {
      fileUrl = "http://" + ipList[0] + "/asn.com/stddownload_common_file?authkey=" + authkey + "&bid=" + DOWNLOAD_FILE.BUSSINESS_ID + "&subbid=" + ctx.sdkAppID + "&fileid=" + uuid + "&filetype=" + DOWNLOAD_FILE_TYPE.FILE + "&openid=" + senderId + "&ver=0&filename=" + encodeURIComponent(fileName);
    } else {
      log.error("拼接文件下载url不报错：ip或者authkey为空");
    }
    Resources.downloadMap["uuid_" + uuid] = fileUrl;
    return fileUrl;
  };

  //获取文件下载地址
  var getFileDownUrlV2 = function (uuid, senderId, fileName, downFlag, receiverId, busiId, type) {
    var options = {
      "From_Account": senderId,//"identifer_0",       // 类型: String, 发送者tinyid
      "To_Account": receiverId,//"identifer_1",         // 类型: String, 接收者tinyid
      "os_platform": 10,                      // 类型: Number, 终端的类型 1(android) 2(ios) 3(windows) 10(others...)
      "Timestamp": unixtime().toString(),     // 类型: Number, 时间戳
      "Random": createRandom().toString(),    // 类型: Number, 随机值
      "request_info": [                       // 类型: Array
        {
          "busi_id": busiId,                   // 类型: Number, 群(1) C2C(2) 其他请联系sdk开发者分配
          "download_flag": downFlag,      // 类型: Number, 申请下载地址标识  0(申请架平下载地址)  1(申请COS平台下载地址)  2(不需要申请, 直接拿url下载(这里应该不会为2))
          "type": type,                      // 类型: Number, 0(短视频缩略图), 1(文件), 2(短视频), 3(ptt), 其他待分配
          "uuid": uuid,                   // 类型: Number, 唯一标识一个文件的uuid
          "version": VERSION_INFO.SERVER_VERSION, // 类型: Number, 架平server版本
          "auth_key": authkey,            // 类型: String, 认证签名
          "ip": ipList[0]                 // 类型: Number, 架平IP
        }
      ]
    };
    //获取下载地址
    proto_applyDownload(options, function (resp) {
      if (resp.error_code == 0 && resp.response_info) {
        Resources.downloadMap["uuid_" + options.uuid] = resp.response_info.url;
      }
      if (onAppliedDownloadUrl) {
        onAppliedDownloadUrl({
          uuid: options.uuid,
          url: resp.response_info.url,
          maps: Resources.downloadMap
        });
      }
    }, function (resp) {
      log.error("获取下载地址失败", options.uuid)
    });
  };


  //重置ajax请求
  var clearXmlHttpObjMap = function () {
    //遍历xmlHttpObjMap{}
    for (var seq in xmlHttpObjMap) {
      var xmlHttpObj = xmlHttpObjMap[seq];
      if (xmlHttpObj) {
        xmlHttpObj.abort();//中断ajax请求(长轮询)
        xmlHttpObjMap[xmlHttpObjSeq] = null;//清空
      }
    }
    xmlHttpObjSeq = 0;
    xmlHttpObjMap = {};
  };

  //重置sdk全局变量
  var clearSdk = function () {

    // clearXmlHttpObjMap();

    //当前登录用户
    ctx = {
      sdkAppID: null,
      appIDAt3rd: null,
      accountType: null,
      identifier: null,
      identifierNick: null,
      userSig: null,
      contentType: 'json',
      apn: 1
    };
    opt = {};

    curSeq = 0;

    //ie8,9采用jsonp方法解决ajax跨域限制
    jsonpRequestId = 0;//jsonp请求id
    //最新jsonp请求返回的json数据
    jsonpLastRspData = null;

    apiReportItems = [];

    MsgManager.clear();
  };

  //登录
  var _login = function (loginInfo, listeners, options, cbOk, cbErr) {

    clearSdk();

    if (options) opt = options;
    if (opt.isAccessFormalEnv == false) {
      isAccessFormaEnvironment = opt.isAccessFormalEnv;
    }
    if (opt.isLogOn == false) {
      log.setOn(opt.isLogOn);
    }
    /*
     if(opt.emotions){
     emotions=opt.emotions;
     webim.Emotions= emotions;
     }
     if(opt.emotionDataIndexs){
     emotionDataIndexs=opt.emotionDataIndexs;
     webim.EmotionDataIndexs= emotionDataIndexs;
     }*/

    if (!loginInfo) {
      if (cbErr) {
        cbErr(tool.getReturnError("loginInfo is empty", -6));
        return;
      }
    }
    if (!loginInfo.sdkAppID) {
      if (cbErr) {
        cbErr(tool.getReturnError("loginInfo.sdkAppID is empty", -7));
        return;
      }
    }
    if (!loginInfo.accountType) {
      if (cbErr) {
        cbErr(tool.getReturnError("loginInfo.accountType is empty", -8));
        return;
      }
    }

    if (loginInfo.identifier) {
      ctx.identifier = loginInfo.identifier.toString();
    }
    if (loginInfo.identifier && !loginInfo.userSig) {
      if (cbErr) {
        cbErr(tool.getReturnError("loginInfo.userSig is empty", -9));
        return;
      }
    }
    if (loginInfo.userSig) {
      ctx.userSig = loginInfo.userSig.toString();
    }
    ctx.sdkAppID = loginInfo.sdkAppID;
    ctx.accountType = loginInfo.accountType;

    if (ctx.identifier && ctx.userSig) {//带登录态
      //登录
      proto_login(
        function (identifierNick) {
          MsgManager.init(
            listeners,
            function (mmInitResp) {
              if (cbOk) {
                mmInitResp.identifierNick = identifierNick;
                cbOk(mmInitResp);
              }
            }, cbErr
          );
        },
        cbErr
      );
    } else {//不带登录态，进入直播场景sdk
      MsgManager.init(
        listeners,
        cbOk,
        cbErr
      );
    }
  };

  //初始化浏览器信息
  var initBrowserInfo = function () {
    //初始化浏览器类型
    BROWSER_INFO = tool.getBrowserInfo();
    log.info('BROWSER_INFO: type=' + BROWSER_INFO.type + ', ver=' + BROWSER_INFO.ver);
    if (BROWSER_INFO.type == "ie") {
      if (parseInt(BROWSER_INFO.ver) < 10) {
        lowerBR = true;
      }
    }
  };

  //接口质量上报
  var reportApiQuality = function (cmd, errorCode, errorInfo) {
    if (cmd == 'longpolling' && (errorCode == longPollingTimeOutErrorCode || errorCode == longPollingKickedErrorCode)) {//longpolling 返回60008错误可以视为正常,可以不上报
      return;
    }
    var eventId = CMD_EVENT_ID_MAP[cmd];
    if (eventId) {
      var reportTime = unixtime();
      var uniqKey = null;
      var msgCmdErrorCode = {
        'Code': errorCode,
        'ErrMsg': errorInfo
      };
      if (ctx.a2) {
        uniqKey = ctx.a2.substring(0, 10) + "_" + reportTime + "_" + createRandom();
      } else if (ctx.userSig) {
        uniqKey = ctx.userSig.substring(0, 10) + "_" + reportTime + "_" + createRandom();
      }

      if (uniqKey) {

        var rptEvtItem = {
          "UniqKey": uniqKey,
          "EventId": eventId,
          "ReportTime": reportTime,
          "MsgCmdErrorCode": msgCmdErrorCode
        };

        if (cmd == 'login') {
          var loginApiReportItems = [];
          loginApiReportItems.push(rptEvtItem);
          var loginReportOpt = {
            "EvtItems": loginApiReportItems,
            "MainVersion": SDK.VERSION,
            "Version": "0"
          };
          proto_reportApiQuality(loginReportOpt,
            function (resp) {
              loginApiReportItems = null;//
            },
            function (err) {
              loginApiReportItems = null;//
            }
          );
        } else {
          apiReportItems.push(rptEvtItem);
          if (apiReportItems.length >= maxApiReportItemCount) {//累计一定条数再上报
            var reportOpt = {
              "EvtItems": apiReportItems,
              "MainVersion": SDK.VERSION,
              "Version": "0"
            };
            proto_reportApiQuality(reportOpt,
              function (resp) {
                apiReportItems = [];//清空
              },
              function (err) {
                apiReportItems = [];//清空
              }
            );
          }
        }

      }
    }
  };

  // REST API calls
  //上线
  var proto_login = function (cbOk, cbErr) {
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "login", { "State": "Online" },
      function (loginResp) {
        if (loginResp.TinyId) {
          ctx.tinyid = loginResp.TinyId;
        } else {
          if (cbErr) {
            cbErr(tool.getReturnError("TinyId is empty", -10));
            return;
          }
        }
        if (loginResp.A2Key) {
          ctx.a2 = loginResp.A2Key;
        } else {
          if (cbErr) {
            cbErr(tool.getReturnError("A2Key is empty", -11));
            return;
          }
        }
        var tag_list = [
          "Tag_Profile_IM_Nick"
        ];
        var options = {
          'From_Account': ctx.identifier,
          'To_Account': [ctx.identifier],
          'LastStandardSequence': 0,
          'TagList': tag_list
        };
        proto_getProfilePortrait(
          options,
          function (resp) {
            var nick, gender, allowType;
            if (resp.UserProfileItem && resp.UserProfileItem.length > 0) {
              for (var i in resp.UserProfileItem) {
                for (var j in resp.UserProfileItem[i].ProfileItem) {
                  switch (resp.UserProfileItem[i].ProfileItem[j].Tag) {
                    case 'Tag_Profile_IM_Nick':
                      nick = resp.UserProfileItem[i].ProfileItem[j].Value;
                      if (nick) ctx.identifierNick = nick;
                      break;
                  }
                }
              }
            }
            if (cbOk) cbOk(ctx.identifierNick);//回传当前用户昵称
          }, cbErr);
      }
      , cbErr);
  };
  //下线
  var proto_logout = function (type, cbOk, cbErr) {
    if (!checkLogin(cbErr, false)) {//不带登录态
      clearSdk();
      if (cbOk) cbOk({
        'ActionStatus': ACTION_STATUS.OK,
        'ErrorCode': 0,
        'ErrorInfo': 'logout success'
      });
      return;
    }
    if (type == "all") {
      ConnManager.apiCall(SRV_NAME.OPEN_IM, "logout", {},
        function (resp) {
          clearSdk();
          if (cbOk) cbOk(resp);
        },
        cbErr);
    } else {
      ConnManager.apiCall(SRV_NAME.OPEN_IM, "longpollinglogout", { LongPollingId: LongPollingId },
        function (resp) {
          clearSdk();
          if (cbOk) cbOk(resp);
        },
        cbErr);
    }
  };
  //发送消息，包括私聊和群聊
  var proto_sendMsg = function (msg, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    var msgInfo = null;

    switch (msg.sess.type()) {
      case SESSION_TYPE.C2C:
        msgInfo = {
          'From_Account': ctx.identifier,
          'To_Account': msg.sess.id().toString(),
          'MsgTimeStamp': msg.time,
          'MsgSeq': msg.seq,
          'MsgRandom': msg.random,
          'MsgBody': []
        };
        break;
      case SESSION_TYPE.GROUP:
        var subType = msg.getSubType();
        msgInfo = {
          'GroupId': msg.sess.id().toString(),
          'From_Account': ctx.identifier,
          'Random': msg.random,
          'MsgBody': []
        };
        switch (subType) {
          case GROUP_MSG_SUB_TYPE.COMMON:
            msgInfo.MsgPriority = "COMMON";
            break;
          case GROUP_MSG_SUB_TYPE.REDPACKET:
            msgInfo.MsgPriority = "REDPACKET";
            break;
          case GROUP_MSG_SUB_TYPE.LOVEMSG:
            msgInfo.MsgPriority = "LOVEMSG";
            break;
          case GROUP_MSG_SUB_TYPE.TIP:
            log.error("不能主动发送群提示消息,subType=" + subType);
            break;
          default:
            log.error("发送群消息时，出现未知子消息类型：subType=" + subType);
            return;
            break;
        }
        break;
      default:
        break;
    }

    for (var i in msg.elems) {
      var elem = msg.elems[i];
      var msgContent = null;
      var msgType = elem.type;
      switch (msgType) {
        case MSG_ELEMENT_TYPE.TEXT://文本
          msgContent = { 'Text': elem.content.text };
          break;
        case MSG_ELEMENT_TYPE.FACE://表情
          msgContent = { 'Index': elem.content.index, 'Data': elem.content.data };
          break;
        case MSG_ELEMENT_TYPE.IMAGE://图片
          var ImageInfoArray = [];
          for (var j in elem.content.ImageInfoArray) {
            ImageInfoArray.push(
              {
                'Type': elem.content.ImageInfoArray[j].type,
                'Size': elem.content.ImageInfoArray[j].size,
                'Width': elem.content.ImageInfoArray[j].width,
                'Height': elem.content.ImageInfoArray[j].height,
                'URL': elem.content.ImageInfoArray[j].url
              }
            );
          }
          msgContent = { 'UUID': elem.content.UUID, 'ImageInfoArray': ImageInfoArray };
          break;
        case MSG_ELEMENT_TYPE.SOUND://
          log.warn('web端暂不支持发送语音消息');
          continue;
          break;
        case MSG_ELEMENT_TYPE.LOCATION://
          log.warn('web端暂不支持发送地理位置消息');
          continue;
          break;
        case MSG_ELEMENT_TYPE.FILE://
          msgContent = {
            'UUID': elem.content.uuid,
            'FileName': elem.content.name,
            'FileSize': elem.content.size,
            'DownloadFlag': elem.content.downFlag
          };
          break;
        case MSG_ELEMENT_TYPE.CUSTOM://
          msgContent = { 'Data': elem.content.data, 'Desc': elem.content.desc, 'Ext': elem.content.ext };
          msgType = MSG_ELEMENT_TYPE.CUSTOM;
          break;
        default:
          log.warn('web端暂不支持发送' + elem.type + '消息');
          continue;
          break;
      }
      msgInfo.MsgBody.push({ 'MsgType': msgType, 'MsgContent': msgContent });
    }
    if (msg.sess.type() == SESSION_TYPE.C2C) {//私聊
      ConnManager.apiCall(SRV_NAME.OPEN_IM, "sendmsg", msgInfo, cbOk, cbErr);
    } else if (msg.sess.type() == SESSION_TYPE.GROUP) {//群聊
      ConnManager.apiCall(SRV_NAME.GROUP, "send_group_msg", msgInfo, cbOk, cbErr);
    }
  };
  //长轮询接口
  var proto_longPolling = function (options, cbOk, cbErr) {
    if (!isAccessFormaEnvironment && typeof stopPolling != "undefined" && stopPolling == true) {
      return;
    }
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "longpolling", options, cbOk, cbErr, longPollingDefaultTimeOut, true);
  };

  //长轮询接口(拉取直播聊天室新消息)
  var proto_bigGroupLongPolling = function (options, cbOk, cbErr, timeout) {
    ConnManager.apiCall(SRV_NAME.BIG_GROUP_LONG_POLLING, "get_msg", options, cbOk, cbErr, timeout);
  };

  //拉取未读c2c消息接口
  var proto_getMsgs = function (cookie, syncFlag, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "getmsg", { 'Cookie': cookie, 'SyncFlag': syncFlag },
      function (resp) {

        if (resp.MsgList && resp.MsgList.length) {
          for (var i in resp.MsgList) {
            tempC2CMsgList.push(resp.MsgList[i]);
          }
        }
        if (resp.SyncFlag == 1) {
          proto_getMsgs(resp.Cookie, resp.SyncFlag, cbOk, cbErr);
        } else {
          resp.MsgList = tempC2CMsgList;
          tempC2CMsgList = [];
          if (cbOk) cbOk(resp);
        }
      },
      cbErr);
  };
  //C2C消息已读上报接口
  var proto_c2CMsgReaded = function (cookie, c2CMsgReadedItem, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    var tmpC2CMsgReadedItem = [];
    for (var i in c2CMsgReadedItem) {
      var item = {
        'To_Account': c2CMsgReadedItem[i].toAccount,
        'LastedMsgTime': c2CMsgReadedItem[i].lastedMsgTime
      };
      tmpC2CMsgReadedItem.push(item);
    }
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "msgreaded", {
      C2CMsgReaded: {
        'Cookie': cookie,
        'C2CMsgReadedItem': tmpC2CMsgReadedItem
      }
    }, cbOk, cbErr);
  };

  //删除c2c消息
  var proto_deleteC2CMsg = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.OPEN_IM, "deletemsg", options,
      cbOk, cbErr);
  };

  //拉取c2c历史消息接口
  var proto_getC2CHistoryMsgs = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "getroammsg", options,
      function (resp) {
        var reqMsgCount = options.MaxCnt;
        var complete = resp.Complete;
        var rspMsgCount = resp.MaxCnt;
        var msgKey = resp.MsgKey;
        var lastMsgTime = resp.LastMsgTime;

        if (resp.MsgList && resp.MsgList.length) {
          for (var i in resp.MsgList) {
            tempC2CHistoryMsgList.push(resp.MsgList[i]);
          }
        }
        var netxOptions = null;
        if (complete == 0) {//还有历史消息可拉取
          if (rspMsgCount < reqMsgCount) {
            netxOptions = {
              'Peer_Account': options.Peer_Account,
              'MaxCnt': reqMsgCount - rspMsgCount,
              'LastMsgTime': lastMsgTime,
              'MsgKey': msgKey
            };
          }
        }

        if (netxOptions) {//继续拉取
          proto_getC2CHistoryMsgs(netxOptions, cbOk, cbErr);
        } else {
          resp.MsgList = tempC2CHistoryMsgList;
          tempC2CHistoryMsgList = [];
          if (cbOk) cbOk(resp);
        }
      },
      cbErr);
  };

  //群组接口
  //创建群组
  //协议参考：https://www.qcloud.com/doc/product/269/1615
  var proto_createGroup = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    var opt = {
      //必填    群组形态，包括Public（公开群），Private（私密群），ChatRoom（聊天室），AVChatRoom（互动直播聊天室）。
      'Type': options.Type,
      //必填    群名称，最长30字节。
      'Name': options.Name
    };
    var member_list = [];

    //Array 选填  初始群成员列表，最多500个。成员信息字段详情参见：群成员资料。
    for (var i = 0; i < options.MemberList.length; i++) {
      member_list.push({ 'Member_Account': options.MemberList[i] })
    }
    opt.MemberList = member_list;
    //选填    为了使得群组ID更加简单，便于记忆传播，腾讯云支持APP在通过REST API创建群组时自定义群组ID。详情参见：自定义群组ID。
    if (options.GroupId) {
      opt.GroupId = options.GroupId;
    }
    //选填    群主id，自动添加到群成员中。如果不填，群没有群主。
    if (options.Owner_Account) {
      opt.Owner_Account = options.Owner_Account;
    }
    //选填    群简介，最长240字节。
    if (options.Introduction) {
      opt.Introduction = options.Introduction;
    }
    //选填    群公告，最长300字节。
    if (options.Notification) {
      opt.Notification = options.Notification;
    }
    //选填    最大群成员数量，最大为10000，不填默认为2000个。
    if (options.MaxMemberCount) {
      opt.MaxMemberCount = options.MaxMemberCount;
    }
    //选填    申请加群处理方式。包含FreeAccess（自由加入），NeedPermission（需要验证），DisableApply（禁止加群），不填默认为NeedPermission（需要验证）。
    if (options.ApplyJoinOption) {//
      opt.ApplyJoinOption = options.ApplyJoinOption;
    }
    //Array 选填  群组维度的自定义字段，默认情况是没有的，需要开通，详情参见：自定义字段。
    if (options.AppDefinedData) {
      opt.AppDefinedData = options.AppDefinedData;
    }
    //选填    群头像URL，最长100字节。
    if (options.FaceUrl) {
      opt.FaceUrl = options.FaceUrl;
    }
    ConnManager.apiCall(SRV_NAME.GROUP, "create_group", opt,
      cbOk, cbErr);
  };

  //创建群组-高级接口
  //协议参考：https://www.qcloud.com/doc/product/269/1615
  var proto_createGroupHigh = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.GROUP, "create_group", options,
      cbOk, cbErr);
  };

  //修改群组基本资料
  //协议参考：https://www.qcloud.com/doc/product/269/1620
  var proto_modifyGroupBaseInfo = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "modify_group_base_info", options,
      cbOk, cbErr);
  };

  //申请加群
  var proto_applyJoinGroup = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "apply_join_group", {
      'GroupId': options.GroupId,
      'ApplyMsg': options.ApplyMsg,
      'UserDefinedField': options.UserDefinedField
    },
      cbOk, cbErr);
  };

  //申请加入大群
  var proto_applyJoinBigGroup = function (options, cbOk, cbErr) {
    var srvName;
    if (!checkLogin(cbErr, false)) {//未登录
      srvName = SRV_NAME.BIG_GROUP;
    } else {//已登录
      srvName = SRV_NAME.GROUP;
    }
    ConnManager.apiCall(srvName, "apply_join_group", {
      'GroupId': options.GroupId,
      'ApplyMsg': options.ApplyMsg,
      'UserDefinedField': options.UserDefinedField
    },
      function (resp) {
        if (resp.JoinedStatus && resp.JoinedStatus == 'JoinedSuccess') {
          if (resp.LongPollingKey) {
            MsgManager.setBigGroupLongPollingOn(true);//开启长轮询
            MsgManager.setBigGroupLongPollingKey(resp.LongPollingKey);//更新大群长轮询key
            MsgManager.setBigGroupLongPollingMsgMap(options.GroupId, 0);//收到的群消息置0
            MsgManager.bigGroupLongPolling();//开启长轮询
          } else {//没有返回LongPollingKey，说明申请加的群不是直播聊天室(AVChatRoom)
            cbErr && cbErr(tool.getReturnError("The type of group is not AVChatRoom: groupid=" + options.GroupId, -12));
            return;
          }
        }
        if (cbOk) cbOk(resp);
      }
      , function (err) {

        if (cbErr) cbErr(err);
      });
  };

  //处理加群申请(同意或拒绝)
  var proto_handleApplyJoinGroupPendency = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "handle_apply_join_group", {
      'GroupId': options.GroupId,
      'Applicant_Account': options.Applicant_Account,
      'HandleMsg': options.HandleMsg,
      'Authentication': options.Authentication,
      'MsgKey': options.MsgKey,
      'ApprovalMsg': options.ApprovalMsg,
      'UserDefinedField': options.UserDefinedField
    },
      cbOk,
      function (err) {
        if (err.ErrorCode == 10024) {//apply has be handled
          if (cbOk) {
            var resp = {
              'ActionStatus': ACTION_STATUS.OK,
              'ErrorCode': 0,
              'ErrorInfo': '该申请已经被处理过'
            };
            cbOk(resp);
          }
        } else {
          if (cbErr) cbErr(err);
        }
      }
    );
  };

  //主动退群
  var proto_quitGroup = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "quit_group", {
      'GroupId': options.GroupId
    },
      cbOk, cbErr);
  };

  //退出大群
  var proto_quitBigGroup = function (options, cbOk, cbErr) {
    var srvName;
    if (!checkLogin(cbErr, false)) {//未登录
      srvName = SRV_NAME.BIG_GROUP;
    } else {//已登录
      srvName = SRV_NAME.GROUP;
    }
    ConnManager.apiCall(srvName, "quit_group",
      { 'GroupId': options.GroupId },
      function (resp) {
        //重置当前再请求中的ajax
        //clearXmlHttpObjMap();
        //退出大群成功之后需要重置长轮询信息
        MsgManager.resetBigGroupLongPollingInfo();
        if (cbOk) cbOk(resp);
      },
      cbErr);
  };
  //查找群(按名称)
  var proto_searchGroupByName = function (options, cbOk, cbErr) {
    ConnManager.apiCall(SRV_NAME.GROUP, "search_group", options, cbOk, cbErr);
  };

  //获取群组公开资料
  var proto_getGroupPublicInfo = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "get_group_public_info", {
      'GroupIdList': options.GroupIdList,
      'ResponseFilter': {
        'GroupBasePublicInfoFilter': options.GroupBasePublicInfoFilter
      }
    },
      function (resp) {
        resp.ErrorInfo = '';
        if (resp.GroupInfo) {
          for (var i in resp.GroupInfo) {
            var errorCode = resp.GroupInfo[i].ErrorCode;
            if (errorCode > 0) {
              resp.ActionStatus = ACTION_STATUS.FAIL;
              resp.GroupInfo[i].ErrorInfo = "[" + errorCode + "]" + resp.GroupInfo[i].ErrorInfo;
              resp.ErrorInfo += resp.GroupInfo[i].ErrorInfo + '\n';
            }
          }
        }
        if (resp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) {
            cbErr(resp);
          }
        } else if (cbOk) {
          cbOk(resp);
        }

      },
      cbErr);
  };

  //获取群组详细资料--高级
  //请求协议参考：https://www.qcloud.com/doc/product/269/1616
  var proto_getGroupInfo = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    var opt = {
      'GroupIdList': options.GroupIdList,
      'ResponseFilter': {
        'GroupBaseInfoFilter': options.GroupBaseInfoFilter,
        'MemberInfoFilter': options.MemberInfoFilter
      }
    };
    if (options.AppDefinedDataFilter_Group) {
      opt.ResponseFilter.AppDefinedDataFilter_Group = options.AppDefinedDataFilter_Group;
    }
    if (options.AppDefinedDataFilter_GroupMember) {
      opt.ResponseFilter.AppDefinedDataFilter_GroupMember = options.AppDefinedDataFilter_GroupMember;
    }
    ConnManager.apiCall(SRV_NAME.GROUP, "get_group_info", opt,
      cbOk, cbErr);
  };

  //获取群组成员-高级接口
  //协议参考：https://www.qcloud.com/doc/product/269/1617
  var proto_getGroupMemberInfo = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "get_group_member_info", {
      'GroupId': options.GroupId,
      'Offset': options.Offset,
      'Limit': options.Limit,
      'MemberInfoFilter': options.MemberInfoFilter,
      'MemberRoleFilter': options.MemberRoleFilter,
      'AppDefinedDataFilter_GroupMember': options.AppDefinedDataFilter_GroupMember
    },
      cbOk, cbErr);
  };


  //增加群组成员
  //协议参考：https://www.qcloud.com/doc/product/269/1621
  var proto_addGroupMember = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "add_group_member", {
      'GroupId': options.GroupId,
      'Silence': options.Silence,
      'MemberList': options.MemberList
    },
      cbOk, cbErr);
  };
  //修改群组成员资料
  //协议参考：https://www.qcloud.com/doc/product/269/1623
  var proto_modifyGroupMember = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    var opt = {};
    if (options.GroupId) {
      opt.GroupId = options.GroupId;
    }
    if (options.Member_Account) {
      opt.Member_Account = options.Member_Account;
    }
    //Admin或者Member
    if (options.Role) {
      opt.Role = options.Role;
    }
    // AcceptAndNotify代表解收并提示消息，Discard代表不接收也不提示消息，AcceptNotNotify代表接收消息但不提示
    if (options.MsgFlag) {
      opt.MsgFlag = options.MsgFlag;
    }
    if (options.ShutUpTime) {//禁言时间
      opt.ShutUpTime = options.ShutUpTime;
    }
    if (options.NameCard) {//群名片,最大不超过50个字节
      opt.NameCard = options.NameCard;
    }
    if (options.AppMemberDefinedData) {//群成员维度的自定义字段，默认情况是没有的，需要开通
      opt.AppMemberDefinedData = options.AppMemberDefinedData;
    }
    ConnManager.apiCall(SRV_NAME.GROUP, "modify_group_member_info", opt,
      cbOk, cbErr);
  };
  //删除群组成员
  //协议参考：https://www.qcloud.com/doc/product/269/1622
  var proto_deleteGroupMember = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "delete_group_member", {
      'GroupId': options.GroupId,
      'Silence': options.Silence,
      'MemberToDel_Account': options.MemberToDel_Account,
      'Reason': options.Reason
    },
      cbOk, cbErr);
  };
  //解散群组
  //协议参考：https://www.qcloud.com/doc/product/269/1624
  var proto_destroyGroup = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "destroy_group", {
      'GroupId': options.GroupId
    },
      cbOk, cbErr);
  };
  //转让群组
  //协议参考：https://www.qcloud.com/doc/product/269/1633
  var proto_changeGroupOwner = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.GROUP, "change_group_owner", options, cbOk, cbErr);
  };
  //获取用户所加入的群组-高级接口
  //协议参考：https://www.qcloud.com/doc/product/269/1625
  var proto_getJoinedGroupListHigh = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "get_joined_group_list", {
      'Member_Account': options.Member_Account,
      'Limit': options.Limit,
      'Offset': options.Offset,
      'GroupType': options.GroupType,
      'ResponseFilter': {
        'GroupBaseInfoFilter': options.GroupBaseInfoFilter,
        'SelfInfoFilter': options.SelfInfoFilter
      }
    },
      cbOk, cbErr);
  };
  //查询一组UserId在群中的身份
  //协议参考：https://www.qcloud.com/doc/product/269/1626
  var proto_getRoleInGroup = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "get_role_in_group", {
      'GroupId': options.GroupId,
      'User_Account': options.User_Account
    },
      cbOk, cbErr);
  };
  //设置取消成员禁言时间
  //协议参考：https://www.qcloud.com/doc/product/269/1627
  var proto_forbidSendMsg = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;

    ConnManager.apiCall(SRV_NAME.GROUP, "forbid_send_msg", {
      'GroupId': options.GroupId,
      'Members_Account': options.Members_Account,
      'ShutUpTime': options.ShutUpTime//单位为秒，为0时表示取消禁言
    },
      cbOk, cbErr);
  };

  //发送自定义群系统通知
  var proto_sendCustomGroupNotify = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.GROUP, "send_group_system_notification", options,
      cbOk, cbErr);
  };

  //拉取群消息接口
  var proto_getGroupMsgs = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.GROUP, "group_msg_get", {
      "GroupId": options.GroupId,
      "ReqMsgSeq": options.ReqMsgSeq,
      "ReqMsgNumber": options.ReqMsgNumber
    },
      cbOk, cbErr);
  };
  //群消息已读上报接口
  var proto_groupMsgReaded = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.GROUP, "msg_read_report", {
      'GroupId': options.GroupId,
      'MsgReadedSeq': options.MsgReadedSeq
    },
      cbOk, cbErr);
  };
  //end

  //好友接口
  //处理好友接口返回的错误码
  var convertErrorEn2ZhFriend = function (resp) {
    var errorAccount = [];
    if (resp.Fail_Account && resp.Fail_Account.length) {
      errorAccount = resp.Fail_Account;
    }
    if (resp.Invalid_Account && resp.Invalid_Account.length) {
      for (var k in resp.Invalid_Account) {
        errorAccount.push(resp.Invalid_Account[k]);
      }
    }
    if (errorAccount.length) {
      resp.ActionStatus = ACTION_STATUS.FAIL;
      resp.ErrorCode = ERROR_CODE_CUSTOM;
      resp.ErrorInfo = '';
      for (var i in errorAccount) {
        var failCount = errorAccount[i];
        for (var j in resp.ResultItem) {
          if (resp.ResultItem[j].To_Account == failCount) {

            var resultCode = resp.ResultItem[j].ResultCode;
            resp.ResultItem[j].ResultInfo = "[" + resultCode + "]" + resp.ResultItem[j].ResultInfo;
            resp.ErrorInfo += resp.ResultItem[j].ResultInfo + "\n";
            break;
          }
        }
      }
    }
    return resp;
  };
  //添加好友
  var proto_applyAddFriend = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "friend_add", {
      'From_Account': ctx.identifier,
      'AddFriendItem': options.AddFriendItem
    },
      function (resp) {
        var newResp = convertErrorEn2ZhFriend(resp);
        if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(newResp);
        } else if (cbOk) {
          cbOk(newResp);
        }
      }, cbErr);
  };
  //删除好友
  var proto_deleteFriend = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "friend_delete", {
      'From_Account': ctx.identifier,
      'To_Account': options.To_Account,
      'DeleteType': options.DeleteType
    },
      function (resp) {
        var newResp = convertErrorEn2ZhFriend(resp);
        if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(newResp);
        } else if (cbOk) {
          cbOk(newResp);
        }
      }, cbErr);
  };
  //获取好友申请
  var proto_getPendency = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "pendency_get", {
      "From_Account": ctx.identifier,
      "PendencyType": options.PendencyType,
      "StartTime": options.StartTime,
      "MaxLimited": options.MaxLimited,
      "LastSequence": options.LastSequence
    },
      cbOk, cbErr);
  };
  //删除好友申请
  var proto_deletePendency = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "pendency_delete", {
      "From_Account": ctx.identifier,
      "PendencyType": options.PendencyType,
      "To_Account": options.To_Account

    },
      function (resp) {
        var newResp = convertErrorEn2ZhFriend(resp);
        if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(newResp);
        } else if (cbOk) {
          cbOk(newResp);
        }
      }, cbErr);
  };
  //处理好友申请
  var proto_responseFriend = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "friend_response", {
      'From_Account': ctx.identifier,
      'ResponseFriendItem': options.ResponseFriendItem
    },
      function (resp) {
        var newResp = convertErrorEn2ZhFriend(resp);
        if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(newResp);
        } else if (cbOk) {
          cbOk(newResp);
        }
      }, cbErr);
  };
  //我的好友
  var proto_getAllFriend = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "friend_get_all", {
      'From_Account': ctx.identifier,
      'TimeStamp': options.TimeStamp,
      'StartIndex': options.StartIndex,
      'GetCount': options.GetCount,
      'LastStandardSequence': options.LastStandardSequence,
      'TagList': options.TagList
    },
      cbOk, cbErr);
  };

  //资料接口
  //查看个人资料
  var proto_getProfilePortrait = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.PROFILE, "portrait_get", {
      'From_Account': ctx.identifier,
      'To_Account': options.To_Account,
      //'LastStandardSequence':options.LastStandardSequence,
      'TagList': options.TagList
    },
      function (resp) {
        var errorAccount = [];
        if (resp.Fail_Account && resp.Fail_Account.length) {
          errorAccount = resp.Fail_Account;
        }
        if (resp.Invalid_Account && resp.Invalid_Account.length) {
          for (var k in resp.Invalid_Account) {
            errorAccount.push(resp.Invalid_Account[k]);
          }
        }
        if (errorAccount.length) {
          resp.ActionStatus = ACTION_STATUS.FAIL;
          resp.ErrorCode = ERROR_CODE_CUSTOM;
          resp.ErrorInfo = '';
          for (var i in errorAccount) {
            var failCount = errorAccount[i];
            for (var j in resp.UserProfileItem) {
              if (resp.UserProfileItem[j].To_Account == failCount) {
                var resultCode = resp.UserProfileItem[j].ResultCode;
                resp.UserProfileItem[j].ResultInfo = "[" + resultCode + "]" + resp.UserProfileItem[j].ResultInfo;
                resp.ErrorInfo += "账号:" + failCount + "," + resp.UserProfileItem[j].ResultInfo + "\n";
                break;
              }
            }
          }
        }
        if (resp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(resp);
        } else if (cbOk) {
          cbOk(resp);
        }
      },
      cbErr);
  };

  //设置个人资料
  var proto_setProfilePortrait = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.PROFILE, "portrait_set",
      {
        'From_Account': ctx.identifier,
        'ProfileItem': options.ProfileItem
      },
      function (resp) {
        for (var i in options.ProfileItem) {
          var profile = options.ProfileItem[i];
          if (profile.Tag == 'Tag_Profile_IM_Nick') {
            ctx.identifierNick = profile.Value;//更新昵称
            break;
          }
        }
        if (cbOk) cbOk(resp);
      }
      , cbErr);
  };

  //增加黑名单
  var proto_addBlackList = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "black_list_add", {
      'From_Account': ctx.identifier,
      'To_Account': options.To_Account
    },
      function (resp) {
        var newResp = convertErrorEn2ZhFriend(resp);
        if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(newResp);
        } else if (cbOk) {
          cbOk(newResp);
        }
      }, cbErr);
  };

  //删除黑名单
  var proto_deleteBlackList = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "black_list_delete", {
      'From_Account': ctx.identifier,
      'To_Account': options.To_Account
    },
      function (resp) {
        var newResp = convertErrorEn2ZhFriend(resp);
        if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
          if (cbErr) cbErr(newResp);
        } else if (cbOk) {
          cbOk(newResp);
        }
      }, cbErr);
  };

  //我的黑名单
  var proto_getBlackList = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.FRIEND, "black_list_get", {
      'From_Account': ctx.identifier,
      'StartIndex': options.StartIndex,
      'MaxLimited': options.MaxLimited,
      'LastSequence': options.LastSequence
    },
      cbOk, cbErr);
  };

  //获取最近联系人
  var proto_getRecentContactList = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.RECENT_CONTACT, "get", {
      'From_Account': ctx.identifier,
      'Count': options.Count
    },
      cbOk, cbErr);
  };

  //上传图片或文件
  var proto_uploadPic = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    if (isAccessFormalEnv()) {
      cmdName = 'pic_up';
    } else {
      cmdName = 'pic_up_test';
    }
    ConnManager.apiCall(SRV_NAME.PIC, cmdName, {
      'App_Version': VERSION_INFO.APP_VERSION,
      'From_Account': ctx.identifier,
      'To_Account': options.To_Account,
      'Seq': options.Seq,
      'Timestamp': options.Timestamp,
      'Random': options.Random,
      'File_Str_Md5': options.File_Str_Md5,
      'File_Size': options.File_Size,
      'File_Type': options.File_Type,
      'Server_Ver': VERSION_INFO.SERVER_VERSION,
      'Auth_Key': authkey,
      'Busi_Id': options.Busi_Id,
      'PkgFlag': options.PkgFlag,
      'Slice_Offset': options.Slice_Offset,
      'Slice_Size': options.Slice_Size,
      'Slice_Data': options.Slice_Data
    },
      cbOk, cbErr);
  };

  //获取语音和文件下载IP和authkey
  var proto_getIpAndAuthkey = function (cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "authkey", {}, cbOk, cbErr);
  };

  //接口质量上报
  var proto_reportApiQuality = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.IM_OPEN_STAT, "web_report", options, cbOk, cbErr);
  };


  var proto_getLongPollingId = function (options, cbOk, cbErr) {
    if (!checkLogin(cbErr, true)) return;
    ConnManager.apiCall(SRV_NAME.OPEN_IM, "getlongpollingid", {},
      function (resp) {
        cbOk && cbOk(resp);
      }, cbErr);
  }


  var proto_applyDownload = function (options, cbOk, cbErr) {
    //把下载地址push到map中
    ConnManager.apiCall(SRV_NAME.PIC, "apply_download", options, cbOk, cbErr);
  }

  //end
  initBrowserInfo();
  // singleton object ConnManager
  var ConnManager = lowerBR == false
    ? new function () {
      var onConnCallback = null;        //回调函数
      this.init = function (onConnNotify, cbOk, cbErr) {
        if (onConnNotify) onConnCallback = onConnNotify;
      };
      this.callBack = function (info) {
        if (onConnCallback) onConnCallback(info);
      };
      this.clear = function () {
        onConnCallback = null;
      };
      //请求后台服务接口
      this.apiCall = function (type, cmd, data, cbOk, cbErr, timeout, isLongPolling) {
        //封装后台服务接口地址
        var url = getApiUrl(type, cmd, cbOk, cbErr);
        if (url == false) return;
        //发起ajax请求
        ajaxRequestJson("POST", url, data, timeout, isLongPolling, function (resp) {
          var errorCode = null, tempErrorInfo = '';
          if (cmd == 'pic_up') {
            data.Slice_Data = '';
          }
          var info = "\n request url: \n" + url + "\n request body: \n" + JSON.stringify(data) + "\n response: \n" + JSON.stringify(resp);
          //成功
          if (resp.ActionStatus == ACTION_STATUS.OK) {
            log.info("[" + type + "][" + cmd + "]success: " + info);
            if (cbOk) cbOk(resp);//回调
            errorCode = 0;
            tempErrorInfo = '';
          } else {
            errorCode = resp.ErrorCode;
            tempErrorInfo = resp.ErrorInfo;
            //报错
            if (cbErr) {
              resp.SrcErrorInfo = resp.ErrorInfo;
              resp.ErrorInfo = "[" + type + "][" + cmd + "]failed: " + info;
              if (cmd != 'longpolling' || resp.ErrorCode != longPollingTimeOutErrorCode) {
                log.error(resp.ErrorInfo);
              }
              cbErr(resp);
            }
          }
          reportApiQuality(cmd, errorCode, tempErrorInfo);//接口质量上报
        }, function (err) {
          cbErr && cbErr(err);
          reportApiQuality(cmd, err.ErrorCode, err.ErrorInfo);//接口质量上报
        });
      };
    }
    : new function () {
      var onConnCallback = null;        //回调函数
      this.init = function (onConnNotify, cbOk, cbErr) {
        if (onConnNotify) onConnCallback = onConnNotify;
      };
      this.callBack = function (info) {
        if (onConnCallback) onConnCallback(info);
      };
      this.clear = function () {
        onConnCallback = null;
      };
      //请求后台服务接口
      this.apiCall = function (type, cmd, data, cbOk, cbErr, timeout, isLongPolling) {
        //封装后台服务接口地址
        var url = getApiUrl(type, cmd, cbOk, cbErr);
        if (url == false) return;
        //发起jsonp请求
        var reqId = jsonpRequestId++,
          cbkey = 'jsonpcallback', // the 'callback' key
          cbval = 'jsonpRequest' + reqId, // the 'callback' value
          script = document.createElement('script'),
          loaded = 0;

        window[cbval] = jsonpCallback;
        script.type = 'text/javascript';
        url = url + "&" + cbkey + "=" + cbval + "&jsonpbody=" + encodeURIComponent(JSON.stringify(data));
        script.src = url;
        script.async = true;

        if (typeof script.onreadystatechange !== 'undefined') {
          // need this for IE due to out-of-order onreadystatechange(), binding script
          // execution to an event listener gives us control over when the script
          // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
          script.event = 'onclick';
          script.htmlFor = script.id = '_jsonpRequest_' + reqId;
        }

        script.onload = script.onreadystatechange = function () {
          if ((this.readyState && this.readyState !== 'complete' && this.readyState !== 'loaded') || loaded) {
            return false;
          }
          script.onload = script.onreadystatechange = null;
          script.onclick && script.onclick();
          // Call the user callback with the last value stored and clean up values and scripts.
          var resp = jsonpLastRspData;
          var info = "\n request url: \n" + url + "\n request body: \n" + JSON.stringify(data) + "\n response: \n" + JSON.stringify(resp);
          if (resp.ActionStatus == ACTION_STATUS.OK) {
            log.info("[" + type + "][" + cmd + "]success: " + info);
            cbOk && cbOk(resp);
          } else {
            resp.ErrorInfo = "[" + type + "][" + cmd + "]failed " + info;
            if (cmd != 'longpolling' || resp.ErrorCode != longPollingTimeOutErrorCode) {
              log.error(resp.ErrorInfo);
            } else {
              log.warn("[" + type + "][" + cmd + "]success: " + info);
            }
            cbErr && cbErr(resp);
          }
          jsonpLastRspData = undefined;
          document.body.removeChild(script);
          loaded = 1;
        };

        // Add the script to the DOM head
        document.body.appendChild(script);
      };
    };
  // class Session
  var Session = function (type, id, name, icon, time, seq) {
    this._impl = {
      skey: Session.skey(type, id),
      type: type,
      id: id,
      name: name,
      icon: icon,
      unread: 0,//未读消息数
      isAutoRead: false,
      time: time >= 0 ? time : 0,
      curMaxMsgSeq: seq >= 0 ? seq : 0,
      msgs: [],
      isFinished: 1
    };
  };
  Session.skey = function (type, id) {
    return type + id;
  };
  Session.prototype.type = function () {
    return this._impl.type;
  };
  Session.prototype.id = function () {
    return this._impl.id;
  };
  Session.prototype.name = function () {
    return this._impl.name;
  };
  Session.prototype.icon = function () {
    return this._impl.icon;
  };
  Session.prototype.unread = function (val) {
    if (typeof val !== 'undefined') {
      this._impl.unread = val;
    } else {
      return this._impl.unread;
    }
  };
  Session.prototype.isFinished = function (val) {
    if (typeof val !== 'undefined') {
      this._impl.isFinished = val;
    } else {
      return this._impl.isFinished;
    }
  };
  Session.prototype.time = function () {
    return this._impl.time;
  };
  Session.prototype.curMaxMsgSeq = function (seq) {
    if (typeof seq !== 'undefined') {
      this._impl.curMaxMsgSeq = seq;
    } else {
      return this._impl.curMaxMsgSeq;
    }
  };
  Session.prototype.msgCount = function () {
    return this._impl.msgs.length;
  };
  Session.prototype.msg = function (index) {
    return this._impl.msgs[index];
  };
  Session.prototype.msgs = function () {
    return this._impl.msgs;
  };
  Session.prototype._impl_addMsg = function (msg) {
    this._impl.msgs.push(msg);
    //if (!msg.isSend && msg.time > this._impl.time)
    if (msg.time > this._impl.time)
      this._impl.time = msg.time;
    //if (!msg.isSend && msg.seq > this._impl.curMaxMsgSeq)
    if (msg.seq > this._impl.curMaxMsgSeq)
      this._impl.curMaxMsgSeq = msg.seq;
    //自己发送的消息不计入未读数
    if (!msg.isSend && !this._impl.isAutoRead) {
      this._impl.unread++;
    }
  };
  //class C2CMsgReadedItem
  var C2CMsgReadedItem = function (toAccount, lastedMsgTime) {
    this.toAccount = toAccount;
    this.lastedMsgTime = lastedMsgTime;
  }
  // class Msg
  var Msg = function (sess, isSend, seq, random, time, fromAccount, subType, fromAccountNick) {
    this.sess = sess;
    this.subType = subType >= 0 ? subType : 0;//消息类型,c2c消息时，type取值为0；group消息时，type取值0和1，0-普通群消息，1-群提示消息
    this.fromAccount = fromAccount;
    this.fromAccountNick = fromAccountNick ? fromAccountNick : fromAccount;
    this.isSend = Boolean(isSend);
    this.seq = seq >= 0 ? seq : nextSeq();
    this.random = random >= 0 ? random : createRandom();
    this.time = time >= 0 ? time : unixtime();
    this.elems = [];
  };
  Msg.prototype.getSession = function () {
    return this.sess;
  };
  Msg.prototype.getType = function () {
    return this.subType;
  };
  Msg.prototype.getSubType = function () {
    return this.subType;
  };
  Msg.prototype.getFromAccount = function () {
    return this.fromAccount;
  };
  Msg.prototype.getFromAccountNick = function () {
    return this.fromAccountNick;
  };
  Msg.prototype.getIsSend = function () {
    return this.isSend;
  };
  Msg.prototype.getSeq = function () {
    return this.seq;
  };
  Msg.prototype.getTime = function () {
    return this.time;
  };
  Msg.prototype.getRandom = function () {
    return this.random;
  };
  Msg.prototype.getElems = function () {
    return this.elems;
  };
  //文本
  Msg.prototype.addText = function (text) {
    this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.TEXT, text));
  };
  //表情
  Msg.prototype.addFace = function (face) {
    this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.FACE, face));
  };
  //图片
  Msg.prototype.addImage = function (image) {
    this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.IMAGE, image));
  };
  //地理位置
  Msg.prototype.addLocation = function (location) {
    this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.LOCATION, location));
  };
  //文件
  Msg.prototype.addFile = function (file) {
    this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.FILE, file));
  };
  //自定义
  Msg.prototype.addCustom = function (custom) {
    this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.CUSTOM, custom));
  };
  Msg.prototype.addElem = function (elem) {
    this.elems.push(elem);
  };
  Msg.prototype.toHtml = function () {
    var html = "";
    for (var i in this.elems) {
      var elem = this.elems[i];
      html += elem.toHtml();
    }
    return html;
  };

  // class Msg.Elem
  Msg.Elem = function (type, value) {
    this.type = type;
    this.content = value;
  };
  Msg.Elem.prototype.getType = function () {
    return this.type;
  };
  Msg.Elem.prototype.getContent = function () {
    return this.content;
  };
  Msg.Elem.prototype.toHtml = function () {
    var html;
    html = this.content.toHtml();
    return html;
  };

  // class Msg.Elem.Text
  Msg.Elem.Text = function (text) {
    this.text = tool.xssFilter(text);
  };
  Msg.Elem.Text.prototype.getText = function () {
    return this.text;
  };
  Msg.Elem.Text.prototype.toHtml = function () {
    return this.text;
  };

  // class Msg.Elem.Face
  Msg.Elem.Face = function (index, data) {
    this.index = index;
    this.data = data;
  };
  Msg.Elem.Face.prototype.getIndex = function () {
    return this.index;
  };
  Msg.Elem.Face.prototype.getData = function () {
    return this.data;
  };
  Msg.Elem.Face.prototype.toHtml = function () {
    var faceUrl = null;
    var index = emotionDataIndexs[this.data];
    var emotion = emotions[index];
    if (emotion && emotion[1]) {
      faceUrl = emotion[1];
    }
    if (faceUrl) {
      return "<img src='" + faceUrl + "'/>";
    } else {
      return this.data;
    }
  };

  // 地理位置消息 class Msg.Elem.Location
  Msg.Elem.Location = function (longitude, latitude, desc) {
    this.latitude = latitude;//纬度
    this.longitude = longitude;//经度
    this.desc = desc;//描述
  };
  Msg.Elem.Location.prototype.getLatitude = function () {
    return this.latitude;
  };
  Msg.Elem.Location.prototype.getLongitude = function () {
    return this.longitude;
  };
  Msg.Elem.Location.prototype.getDesc = function () {
    return this.desc;
  };
  Msg.Elem.Location.prototype.toHtml = function () {
    return '经度=' + this.longitude + ',纬度=' + this.latitude + ',描述=' + this.desc;
  };

  //图片消息
  // class Msg.Elem.Images
  Msg.Elem.Images = function (imageId) {
    this.UUID = imageId;
    this.ImageInfoArray = [];
  };
  Msg.Elem.Images.prototype.addImage = function (image) {
    this.ImageInfoArray.push(image);
  };
  Msg.Elem.Images.prototype.toHtml = function () {
    var smallImage = this.getImage(IMAGE_TYPE.SMALL);
    var bigImage = this.getImage(IMAGE_TYPE.LARGE);
    var oriImage = this.getImage(IMAGE_TYPE.ORIGIN);
    if (!bigImage) {
      bigImage = smallImage;
    }
    if (!oriImage) {
      oriImage = smallImage;
    }
    return "<img src='" + smallImage.getUrl() + "#" + bigImage.getUrl() + "#" + oriImage.getUrl() + "' style='CURSOR: hand' id='" + this.getImageId() + "' bigImgUrl='" + bigImage.getUrl() + "' onclick='imageClick(this)' />";

  };
  Msg.Elem.Images.prototype.getImageId = function () {
    return this.UUID;
  };
  Msg.Elem.Images.prototype.getImage = function (type) {
    for (var i in this.ImageInfoArray) {
      if (this.ImageInfoArray[i].getType() == type) {
        return this.ImageInfoArray[i];
      }
    }
    return null;
  };
  // class Msg.Elem.Images.Image
  Msg.Elem.Images.Image = function (type, size, width, height, url) {
    this.type = type;
    this.size = size;
    this.width = width;
    this.height = height;
    this.url = url;
  };
  Msg.Elem.Images.Image.prototype.getType = function () {
    return this.type;
  };
  Msg.Elem.Images.Image.prototype.getSize = function () {
    return this.size;
  };
  Msg.Elem.Images.Image.prototype.getWidth = function () {
    return this.width;
  };
  Msg.Elem.Images.Image.prototype.getHeight = function () {
    return this.height;
  };
  Msg.Elem.Images.Image.prototype.getUrl = function () {
    return this.url;
  };

  // class Msg.Elem.Sound
  Msg.Elem.Sound = function (uuid, second, size, senderId, receiverId, downFlag, chatType) {
    this.uuid = uuid;//文件id
    this.second = second;//时长，单位：秒
    this.size = size;//大小，单位：字节
    this.senderId = senderId;//发送者
    this.receiverId = receiverId;//接收方id
    this.downFlag = downFlag;//下载标志位
    this.busiId = chatType == SESSION_TYPE.C2C ? 2 : 1;//busi_id ( 1：群    2:C2C)

    //根据不同情况拉取数据
    //是否需要申请下载地址  0:到架平申请  1:到cos申请  2:不需要申请, 直接拿url下载
    if (downFlag !== undefined && busiId !== undefined) {
      getFileDownUrlV2(uuid, senderId, second, downFlag, receiverId, this.busiId, UPLOAD_RES_TYPE.SOUND);
    } else {
      this.downUrl = getSoundDownUrl(uuid, senderId, second);//下载地址
    }
  };
  Msg.Elem.Sound.prototype.getUUID = function () {
    return this.uuid;
  };
  Msg.Elem.Sound.prototype.getSecond = function () {
    return this.second;
  };
  Msg.Elem.Sound.prototype.getSize = function () {
    return this.size;
  };
  Msg.Elem.Sound.prototype.getSenderId = function () {
    return this.senderId;
  };
  Msg.Elem.Sound.prototype.getDownUrl = function () {
    return this.downUrl;
  };
  Msg.Elem.Sound.prototype.toHtml = function () {
    if (BROWSER_INFO.type == 'ie' && parseInt(BROWSER_INFO.ver) <= 8) {
      return '[这是一条语音消息]demo暂不支持ie8(含)以下浏览器播放语音,语音URL:' + this.downUrl;
    }
    return '<audio id="uuid_' + this.uuid + '" src="' + this.downUrl + '" controls="controls" onplay="onChangePlayAudio(this)" preload="none"></audio>';
  };

  // class Msg.Elem.File
  Msg.Elem.File = function (uuid, name, size, senderId, receiverId, downFlag, chatType) {
    this.uuid = uuid;//文件id
    this.name = name;//文件名
    this.size = size;//大小，单位：字节
    this.senderId = senderId;//发送者
    this.receiverId = receiverId;//接收方id
    this.downFlag = downFlag;//下载标志位

    this.busiId = chatType == SESSION_TYPE.C2C ? 2 : 1;//busi_id ( 1：群    2:C2C)
    //根据不同情况拉取数据
    //是否需要申请下载地址  0:到架平申请  1:到cos申请  2:不需要申请, 直接拿url下载
    if (downFlag !== undefined && busiId !== undefined) {
      getFileDownUrlV2(uuid, senderId, name, downFlag, receiverId, this.busiId, UPLOAD_RES_TYPE.FILE);
    } else {
      this.downUrl = getFileDownUrl(uuid, senderId, name);//下载地址
    }
  };
  Msg.Elem.File.prototype.getUUID = function () {
    return this.uuid;
  };
  Msg.Elem.File.prototype.getName = function () {
    return this.name;
  };
  Msg.Elem.File.prototype.getSize = function () {
    return this.size;
  };
  Msg.Elem.File.prototype.getSenderId = function () {
    return this.senderId;
  };
  Msg.Elem.File.prototype.getDownUrl = function () {
    return this.downUrl;
  };
  Msg.Elem.File.prototype.getDownFlag = function () {
    return this.downFlag;
  };
  Msg.Elem.File.prototype.toHtml = function () {
    var fileSize, unitStr;
    fileSize = this.size;
    unitStr = "Byte";
    if (this.size >= 1024) {
      fileSize = Math.round(this.size / 1024);
      unitStr = "KB";
    }
    return '<a href="javascript" onclick="webim.onDownFile("' + this.uuid + '")" title="点击下载文件" ><i class="glyphicon glyphicon-file">&nbsp;' + this.name + '(' + fileSize + unitStr + ')</i></a>';
  };

  // class Msg.Elem.GroupTip 群提示消息对象
  Msg.Elem.GroupTip = function (opType, opUserId, groupId, groupName, userIdList) {
    this.opType = opType;//操作类型
    this.opUserId = opUserId;//操作者id
    this.groupId = groupId;//群id
    this.groupName = groupName;//群名称
    this.userIdList = userIdList ? userIdList : [];//被操作的用户id列表
    this.groupInfoList = [];//新的群资料信息，群资料变更时才有值
    this.memberInfoList = [];//新的群成员资料信息，群成员资料变更时才有值
    this.groupMemberNum = null;//群成员数，操作类型为加群或者退群时才有值
  };
  Msg.Elem.GroupTip.prototype.addGroupInfo = function (groupInfo) {
    this.groupInfoList.push(groupInfo);
  };
  Msg.Elem.GroupTip.prototype.addMemberInfo = function (memberInfo) {
    this.memberInfoList.push(memberInfo);
  };
  Msg.Elem.GroupTip.prototype.getOpType = function () {
    return this.opType;
  };
  Msg.Elem.GroupTip.prototype.getOpUserId = function () {
    return this.opUserId;
  };
  Msg.Elem.GroupTip.prototype.getGroupId = function () {
    return this.groupId;
  };
  Msg.Elem.GroupTip.prototype.getGroupName = function () {
    return this.groupName;
  };
  Msg.Elem.GroupTip.prototype.getUserIdList = function () {
    return this.userIdList;
  };
  Msg.Elem.GroupTip.prototype.getGroupInfoList = function () {
    return this.groupInfoList;
  };
  Msg.Elem.GroupTip.prototype.getMemberInfoList = function () {
    return this.memberInfoList;
  };
  Msg.Elem.GroupTip.prototype.getGroupMemberNum = function () {
    return this.groupMemberNum;
  };
  Msg.Elem.GroupTip.prototype.setGroupMemberNum = function (groupMemberNum) {
    return this.groupMemberNum = groupMemberNum;
  };
  Msg.Elem.GroupTip.prototype.toHtml = function () {
    var text = "[群提示消息]";
    var maxIndex = GROUP_TIP_MAX_USER_COUNT - 1;
    switch (this.opType) {
      case GROUP_TIP_TYPE.JOIN://加入群
        text += this.opUserId + "邀请了";
        for (var m in this.userIdList) {
          text += this.userIdList[m] + ",";
          if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
            text += "等" + this.userIdList.length + "人";
            break;
          }
        }
        text += "加入该群";
        break;
      case GROUP_TIP_TYPE.QUIT://退出群
        text += this.opUserId + "主动退出该群";
        break;
      case GROUP_TIP_TYPE.KICK://踢出群
        text += this.opUserId + "将";
        for (var m in this.userIdList) {
          text += this.userIdList[m] + ",";
          if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
            text += "等" + this.userIdList.length + "人";
            break;
          }
        }
        text += "踢出该群";
        break;
      case GROUP_TIP_TYPE.SET_ADMIN://设置管理员
        text += this.opUserId + "将";
        for (var m in this.userIdList) {
          text += this.userIdList[m] + ",";
          if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
            text += "等" + this.userIdList.length + "人";
            break;
          }
        }
        text += "设为管理员";
        break;
      case GROUP_TIP_TYPE.CANCEL_ADMIN://取消管理员
        text += this.opUserId + "取消";
        for (var m in this.userIdList) {
          text += this.userIdList[m] + ",";
          if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
            text += "等" + this.userIdList.length + "人";
            break;
          }
        }
        text += "的管理员资格";
        break;


      case GROUP_TIP_TYPE.MODIFY_GROUP_INFO://群资料变更
        text += this.opUserId + "修改了群资料：";
        for (var m in this.groupInfoList) {
          var type = this.groupInfoList[m].getType();
          var value = this.groupInfoList[m].getValue();
          switch (type) {
            case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.FACE_URL:
              text += "群头像为" + value + "; ";
              break;
            case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NAME:
              text += "群名称为" + value + "; ";
              break;
            case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.OWNER:
              text += "群主为" + value + "; ";
              break;
            case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NOTIFICATION:
              text += "群公告为" + value + "; ";
              break;
            case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.INTRODUCTION:
              text += "群简介为" + value + "; ";
              break;
            default:
              text += "未知信息为:type=" + type + ",value=" + value + "; ";
              break;
          }
        }
        break;

      case GROUP_TIP_TYPE.MODIFY_MEMBER_INFO://群成员资料变更(禁言时间)
        text += this.opUserId + "修改了群成员资料:";
        for (var m in this.memberInfoList) {
          var userId = this.memberInfoList[m].getUserId();
          var shutupTime = this.memberInfoList[m].getShutupTime();
          text += userId + ": ";
          if (shutupTime != null && shutupTime !== undefined) {
            if (shutupTime == 0) {
              text += "取消禁言; ";
            } else {
              text += "禁言" + shutupTime + "秒; ";
            }
          } else {
            text += " shutupTime为空";
          }
          if (this.memberInfoList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
            text += "等" + this.memberInfoList.length + "人";
            break;
          }
        }
        break;

      case GROUP_TIP_TYPE.READED://消息已读
        /**/
        Log.info("消息已读同步")
        break;
      default:
        text += "未知群提示消息类型：type=" + this.opType;
        break;
    }
    return text;
  };

  // class Msg.Elem.GroupTip.GroupInfo，变更的群资料信息对象
  Msg.Elem.GroupTip.GroupInfo = function (type, value) {
    this.type = type;//群资料信息类型
    this.value = value;//对应的值
  };
  Msg.Elem.GroupTip.GroupInfo.prototype.getType = function () {
    return this.type;
  };
  Msg.Elem.GroupTip.GroupInfo.prototype.getValue = function () {
    return this.value;
  };

  // class Msg.Elem.GroupTip.MemberInfo，变更的群成员资料信息对象
  Msg.Elem.GroupTip.MemberInfo = function (userId, shutupTime) {
    this.userId = userId;//群成员id
    this.shutupTime = shutupTime;//群成员被禁言时间，0表示取消禁言，大于0表示被禁言时长，单位：秒
  };
  Msg.Elem.GroupTip.MemberInfo.prototype.getUserId = function () {
    return this.userId;
  };
  Msg.Elem.GroupTip.MemberInfo.prototype.getShutupTime = function () {
    return this.shutupTime;
  };

  // 自定义消息类型 class Msg.Elem.Custom
  Msg.Elem.Custom = function (data, desc, ext) {
    this.data = data;//数据
    this.desc = desc;//描述
    this.ext = ext;//扩展字段
  };
  Msg.Elem.Custom.prototype.getData = function () {
    return this.data;
  };
  Msg.Elem.Custom.prototype.getDesc = function () {
    return this.desc;
  };
  Msg.Elem.Custom.prototype.getExt = function () {
    return this.ext;
  };
  Msg.Elem.Custom.prototype.toHtml = function () {
    return this.data;
  };

  // singleton object MsgStore
  var MsgStore = new function () {
    var sessMap = {};//跟所有用户或群的聊天记录MAP
    var sessTimeline = [];//按时间降序排列的会话列表
    msgCache = {};//消息缓存，用于判重
    //C2C
    this.cookie = "";//上一次拉取新c2c消息的cookie
    this.syncFlag = 0;//上一次拉取新c2c消息的是否继续拉取标记

    var visitSess = function (visitor) {
      for (var i in sessMap) {
        visitor(sessMap[i]);
      }
    };
    // window.msgCache = msgCache;
    //消息查重
    var checkDupMsg = function (msg) {
      var dup = false;
      var first_key = msg.sess._impl.skey;
      var second_key = msg.isSend + msg.seq + msg.random;
      var tempMsg = msgCache[first_key] && msgCache[first_key][second_key];
      if (tempMsg) {
        dup = true;
      }
      if (msgCache[first_key]) {
        msgCache[first_key][second_key] = { time: msg.time };
      } else {
        msgCache[first_key] = {};
        msgCache[first_key][second_key] = { time: msg.time };
      }
      return dup;
    };

    this.sessMap = function () {
      return sessMap;
    };
    this.sessCount = function () {
      return sessTimeline.length;
    };
    this.sessByTypeId = function (type, id) {
      var skey = Session.skey(type, id);
      if (skey === undefined || skey == null) return null;
      return sessMap[skey];
    };
    this.delSessByTypeId = function (type, id) {
      var skey = Session.skey(type, id);
      if (skey === undefined || skey == null) return false;
      if (sessMap[skey]) {
        delete sessMap[skey];
        delete msgCache[skey];
      }
      return true;
    };
    this.resetCookieAndSyncFlag = function () {
      this.cookie = "";
      this.syncFlag = 0;
    };

    //切换将当前会话的自动读取消息标志为isOn,重置其他会话的自动读取消息标志为false
    this.setAutoRead = function (selSess, isOn, isResetAll) {
      if (isResetAll)
        visitSess(function (s) {
          s._impl.isAutoRead = false;
        });
      if (selSess) {
        selSess._impl.isAutoRead = isOn;//
        if (isOn) {//是否调用已读上报接口
          selSess._impl.unread = 0;

          if (selSess._impl.type == SESSION_TYPE.C2C) {//私聊消息已读上报
            var tmpC2CMsgReadedItem = [];
            tmpC2CMsgReadedItem.push(new C2CMsgReadedItem(selSess._impl.id, selSess._impl.time));
            //调用C2C消息已读上报接口
            proto_c2CMsgReaded(MsgStore.cookie,
              tmpC2CMsgReadedItem,
              function (resp) {
                log.info("[setAutoRead]: c2CMsgReaded success");
              },
              function (err) {
                log.error("[setAutoRead}: c2CMsgReaded failed:" + err.ErrorInfo);
              });
          } else if (selSess._impl.type == SESSION_TYPE.GROUP) {//群聊消息已读上报
            var tmpOpt = {
              'GroupId': selSess._impl.id,
              'MsgReadedSeq': selSess._impl.curMaxMsgSeq
            };
            //调用group消息已读上报接口
            proto_groupMsgReaded(tmpOpt,
              function (resp) {
                log.info("groupMsgReaded success");

              },
              function (err) {
                log.error("groupMsgReaded failed:" + err.ErrorInfo);

              });
          }
        }
      }
    };

    this.c2CMsgReaded = function (opts, cbOk, cbErr) {
      var tmpC2CMsgReadedItem = [];
      tmpC2CMsgReadedItem.push(new C2CMsgReadedItem(opts.To_Account, opts.LastedMsgTime));
      //调用C2C消息已读上报接口
      proto_c2CMsgReaded(MsgStore.cookie,
        tmpC2CMsgReadedItem,
        function (resp) {
          if (cbOk) {
            log.info("c2CMsgReaded success");
            cbOk(resp);
          }
        },
        function (err) {
          if (cbErr) {
            log.error("c2CMsgReaded failed:" + err.ErrorInfo);
            cbErr(err);
          }
        });
    };

    this.addSession = function (sess) {
      sessMap[sess._impl.skey] = sess;
    };
    this.delSession = function (sess) {
      delete sessMap[sess._impl.skey];
    };
    this.addMsg = function (msg) {
      if (checkDupMsg(msg)) return false;
      var sess = msg.sess;
      if (!sessMap[sess._impl.skey]) this.addSession(sess);
      sess._impl_addMsg(msg);
      return true;
    };
    this.updateTimeline = function () {
      var arr = new Array;
      visitSess(function (sess) {
        arr.push(sess);
      });
      arr.sort(function (a, b) {
        return b.time - a.time;
      });
      sessTimeline = arr;
    };
  };
  // singleton object MsgManager
  var MsgManager = new function () {

    var onMsgCallback = null;//新消息(c2c和group)回调

    var onGroupInfoChangeCallback = null;//群资料变化回调
    //收到新群系统消息回调列表
    var onGroupSystemNotifyCallbacks = {
      "1": null,
      "2": null,
      "3": null,
      "4": null,
      "5": null,
      "6": null,
      "7": null,
      "8": null,
      "9": null,
      "10": null,
      "11": null,
      "15": null,
      "255": null
    };
    //监听好友系统通知函数
    var onFriendSystemNotifyCallbacks = {
      "1": null,
      "2": null,
      "3": null,
      "4": null,
      "5": null,
      "6": null,
      "7": null,
      "8": null
    };

    var onProfileSystemNotifyCallbacks = {
      "1": null
    };

    var onMsgReadCallback = null;

    //普通长轮询
    var longPollingOn = false;//是否开启普通长轮询
    var isLongPollingRequesting = false;//是否在长轮询ing
    var notifySeq = 0;//c2c通知seq
    var noticeSeq = 0;//群消息seq

    //大群长轮询
    var onBigGroupMsgCallback = null;//大群消息回调
    var bigGroupLongPollingOn = false;//是否开启长轮询
    var bigGroupLongPollingStartSeq = 0;//请求拉消息的起始seq(大群长轮询)
    var bigGroupLongPollingHoldTime = 90;//客户端长轮询的超时时间，单位是秒(大群长轮询)
    var bigGroupLongPollingKey = null;//客户端加入群组后收到的的Key(大群长轮询)
    var bigGroupLongPollingMsgMap = {};//记录收到的群消息数


    var getLostGroupMsgCount = 0;//补拉丢失的群消息次数
    //我的群当前最大的seq
    var myGroupMaxSeqs = {};//用于补拉丢失的群消息

    var groupSystemMsgsCache = {};//群组系统消息缓存,用于判重

    //设置长轮询开关
    //isOn=true 开启
    //isOn=false 停止
    this.setLongPollingOn = function (isOn) {
      longPollingOn = isOn;
    };
    this.getLongPollingOn = function () {
      return longPollingOn;
    };

    //重置长轮询变量
    this.resetLongPollingInfo = function () {
      longPollingOn = false;
      notifySeq = 0;
      noticeSeq = 0;
    };

    //设置大群长轮询开关
    //isOn=true 开启
    //isOn=false 停止
    this.setBigGroupLongPollingOn = function (isOn) {
      bigGroupLongPollingOn = isOn;
    };
    //设置大群长轮询key
    this.setBigGroupLongPollingKey = function (key) {
      bigGroupLongPollingKey = key;
    };
    //重置大群长轮询变量
    this.resetBigGroupLongPollingInfo = function () {
      bigGroupLongPollingOn = false;
      bigGroupLongPollingStartSeq = 0;
      bigGroupLongPollingKey = null;
      bigGroupLongPollingMsgMap = {};
    };

    //设置群消息数据条数
    this.setBigGroupLongPollingMsgMap = function (groupId, count) {
      var bigGroupLongPollingMsgCount = bigGroupLongPollingMsgMap[groupId];
      if (bigGroupLongPollingMsgCount) {
        bigGroupLongPollingMsgCount = parseInt(bigGroupLongPollingMsgCount) + count;
        bigGroupLongPollingMsgMap[groupId] = bigGroupLongPollingMsgCount;
      } else {
        bigGroupLongPollingMsgMap[groupId] = count;
      }
    };

    //重置
    this.clear = function () {

      onGroupInfoChangeCallback = null;
      onGroupSystemNotifyCallbacks = {
        "1": null,//申请加群请求（只有管理员会收到）
        "2": null,//申请加群被同意（只有申请人能够收到）
        "3": null,//申请加群被拒绝（只有申请人能够收到）
        "4": null,//被管理员踢出群(只有被踢者接收到)
        "5": null,//群被解散(全员接收)
        "6": null,//创建群(创建者接收)
        "7": null,//邀请加群(被邀请者接收)
        "8": null,//主动退群(主动退出者接收)
        "9": null,//设置管理员(被设置者接收)
        "10": null,//取消管理员(被取消者接收)
        "11": null,//群已被回收(全员接收)
        "15": null,//群已被回收(全员接收)
        "255": null//用户自定义通知(默认全员接收)
      };
      onFriendSystemNotifyCallbacks = {
        "1": null,//好友表增加
        "2": null,//好友表删除
        "3": null,//未决增加
        "4": null,//未决删除
        "5": null,//黑名单增加
        "6": null,//黑名单删除
        "7": null,//未决已读上报
        "8": null//好友信息(备注，分组)变更
      };
      onProfileSystemNotifyCallbacks = {
        "1": null//资料修改
      };
      //重置普通长轮询参数
      onMsgCallback = null;
      longPollingOn = false;
      notifySeq = 0;//c2c新消息通知seq
      noticeSeq = 0;//group新消息seq

      //重置大群长轮询参数
      onBigGroupMsgCallback = null;
      bigGroupLongPollingOn = false;
      bigGroupLongPollingStartSeq = 0;
      bigGroupLongPollingKey = null;
      bigGroupLongPollingMsgMap = {};

      groupSystemMsgsCache = {};

      ipList = [];//文件下载地址
      authkey = null;//文件下载票据
      expireTime = null;//票据超时时间
    };

    //初始化文件下载ip和票据
    var initIpAndAuthkey = function (cbOk, cbErr) {
      proto_getIpAndAuthkey(function (resp) {
        ipList = resp.IpList;
        authkey = resp.AuthKey;
        expireTime = resp.ExpireTime;
        if (cbOk) cbOk(resp);
      },
        function (err) {
          log.error("initIpAndAuthkey failed:" + err.ErrorInfo);
          if (cbErr) cbErr(err);
        }
      );
    };

    //初始化我的群当前最大的seq，用于补拉丢失的群消息
    var initMyGroupMaxSeqs = function (cbOk, cbErr) {
      var opts = {
        'Member_Account': ctx.identifier,
        'Limit': 1000,
        'Offset': 0,
        'GroupBaseInfoFilter': [
          'NextMsgSeq'
        ]
      };
      proto_getJoinedGroupListHigh(opts, function (resp) {
        if (!resp.GroupIdList || resp.GroupIdList.length == 0) {
          log.info("initMyGroupMaxSeqs: 目前还没有加入任何群组");
          if (cbOk) cbOk(resp);
          return;
        }
        for (var i = 0; i < resp.GroupIdList.length; i++) {
          var group_id = resp.GroupIdList[i].GroupId;
          var curMaxSeq = resp.GroupIdList[i].NextMsgSeq - 1;
          myGroupMaxSeqs[group_id] = curMaxSeq;
        }

        if (cbOk) cbOk(resp);

      },
        function (err) {
          log.error("initMyGroupMaxSeqs failed:" + err.ErrorInfo);
          if (cbErr) cbErr(err);
        }
      );
    };

    //补拉群消息
    var getLostGroupMsgs = function (groupId, reqMsgSeq, reqMsgNumber) {
      getLostGroupMsgCount++;
      //发起一个拉群群消息请求
      var tempOpts = {
        'GroupId': groupId,
        'ReqMsgSeq': reqMsgSeq,
        'ReqMsgNumber': reqMsgNumber
      };
      //发起一个拉群群消息请求
      log.warn("第" + getLostGroupMsgCount + "次补齐群消息,参数=" + JSON.stringify(tempOpts));
      MsgManager.syncGroupMsgs(tempOpts);
    };

    //更新群当前最大消息seq
    var updateMyGroupCurMaxSeq = function (groupId, msgSeq) {
      //更新myGroupMaxSeqs中的群最大seq
      var curMsgSeq = myGroupMaxSeqs[groupId]
      if (curMsgSeq) {//如果存在，比较大小
        if (msgSeq > curMsgSeq) {
          myGroupMaxSeqs[groupId] = msgSeq;
        }
      } else {//不存在，新增
        myGroupMaxSeqs[groupId] = msgSeq;
      }
    };

    //添加群消息列表
    var addGroupMsgList = function (msgs, new_group_msgs) {
      for (var p in msgs) {
        var newGroupMsg = msgs[p];
        //发群消息时，长轮询接口会返回用户自己发的群消息
        //if(newGroupMsg.From_Account && newGroupMsg.From_Account!=ctx.identifier ){
        if (newGroupMsg.From_Account) {
          //false-不是主动拉取的历史消息
          //true-需要保存到sdk本地session,并且需要判重
          var msg = handlerGroupMsg(newGroupMsg, false, true);
          if (msg) {//不为空，加到新消息里
            new_group_msgs.push(msg);
          }
          //更新myGroupMaxSeqs中的群最大seq
          updateMyGroupCurMaxSeq(newGroupMsg.ToGroupId, newGroupMsg.MsgSeq);
        }
      }
      return new_group_msgs;
    };

    //处理收到的群普通和提示消息
    var handlerOrdinaryAndTipC2cMsgs = function (eventType, groupMsgArray) {
      var groupMsgMap = {};//保存收到的C2c消息信息（群号，最小，最大消息seq，消息列表）
      var new_group_msgs = [];
      var minGroupMsgSeq = 99999999;
      var maxGroupMsgSeq = -1;
      for (var j in groupMsgArray) {

        var groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId];
        if (!groupMsgs) {
          groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId] = {
            "min": minGroupMsgSeq,//收到新消息最小seq
            "max": maxGroupMsgSeq,//收到新消息最大seq
            "msgs": []//收到的新消息
          };
        }
        //更新长轮询的群NoticeSeq
        if (groupMsgArray[j].NoticeSeq > noticeSeq) {
          log.warn("noticeSeq=" + noticeSeq + ",msgNoticeSeq=" + groupMsgArray[j].NoticeSeq);
          noticeSeq = groupMsgArray[j].NoticeSeq;
        }
        groupMsgArray[j].Event = eventType;
        groupMsgMap[groupMsgArray[j].ToGroupId].msgs.push(groupMsgArray[j]);//新增一条消息
        if (groupMsgArray[j].MsgSeq < groupMsgs.min) {//记录最小的消息seq
          groupMsgMap[groupMsgArray[j].ToGroupId].min = groupMsgArray[j].MsgSeq;
        }
        if (groupMsgArray[j].MsgSeq > groupMsgs.max) {//记录最大的消息seq
          groupMsgMap[groupMsgArray[j].ToGroupId].max = groupMsgArray[j].MsgSeq;
        }
      }

      for (var groupId in groupMsgMap) {
        var tempCount = groupMsgMap[groupId].max - groupMsgMap[groupId].min + 1;//收到的新的群消息数
        var curMaxMsgSeq = myGroupMaxSeqs[groupId];//获取本地保存的群最大消息seq
        if (curMaxMsgSeq) {//存在这个群的最大消息seq
          //高并发情况下，长轮询可能存在丢消息，这时需要客户端通过拉取群消息接口补齐下
          //1、如果收到的新消息最小seq比当前最大群消息seq大于1，则表示收到的群消息发生跳跃，需要补齐
          //2、收到的新群消息seq存在不连续情况，也需要补齐
          if (groupMsgMap[groupId].min - curMaxMsgSeq > 1 || groupMsgMap[groupId].msgs.length < tempCount) {
            //发起一个拉群群消息请求
            log.warn("发起一次补齐群消息请求,curMaxMsgSeq=" + curMaxMsgSeq + ", minMsgSeq=" + groupMsgMap[groupId].min + ", maxMsgSeq=" + groupMsgMap[groupId].max + ", msgs.length=" + groupMsgMap[groupId].msgs.length + ", tempCount=" + tempCount);
            getLostGroupMsgs(groupId, groupMsgMap[groupId].max, groupMsgMap[groupId].max - curMaxMsgSeq);
            //更新myGroupMaxSeqs中的群最大seq
            updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
          } else {
            new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
          }
        } else {//不存在该群的最大消息seq
          log.warn("不存在该群的最大消息seq，群id=" + groupId);
          //高并发情况下，长轮询可能存在丢消息，这时需要客户端通过拉取群消息接口补齐下
          //1、收到的新群消息seq存在不连续情况，也需要补齐
          if (groupMsgMap[groupId].msgs.length < tempCount) {
            //发起一个拉群群消息请求
            log.warn("发起一次补齐群消息请求,minMsgSeq=" + groupMsgMap[groupId].min + ", maxMsgSeq=" + groupMsgMap[groupId].max + ", msgs.length=" + groupMsgMap[groupId].msgs.length + ", tempCount=" + tempCount);
            getLostGroupMsgs(groupId, groupMsgMap[groupId].max, tempCount);
            //更新myGroupMaxSeqs中的群最大seq
            updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
          } else {
            new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
          }
        }
      }
      if (new_group_msgs.length) {
        MsgStore.updateTimeline();
      }
      if (onMsgCallback && new_group_msgs.length) onMsgCallback(new_group_msgs);

    };

    //处理收到的群普通和提示消息
    var handlerOrdinaryAndTipGroupMsgs = function (eventType, groupMsgArray) {
      var groupMsgMap = {};//保存收到的群消息信息（群号，最小，最大消息seq，消息列表）
      var new_group_msgs = [];
      var minGroupMsgSeq = 99999999;
      var maxGroupMsgSeq = -1;
      for (var j in groupMsgArray) {

        var groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId];
        if (!groupMsgs) {
          groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId] = {
            "min": minGroupMsgSeq,//收到新消息最小seq
            "max": maxGroupMsgSeq,//收到新消息最大seq
            "msgs": []//收到的新消息
          };
        }
        //更新长轮询的群NoticeSeq
        if (groupMsgArray[j].NoticeSeq > noticeSeq) {
          log.warn("noticeSeq=" + noticeSeq + ",msgNoticeSeq=" + groupMsgArray[j].NoticeSeq);
          noticeSeq = groupMsgArray[j].NoticeSeq;
        }
        groupMsgArray[j].Event = eventType;
        groupMsgMap[groupMsgArray[j].ToGroupId].msgs.push(groupMsgArray[j]);//新增一条消息
        if (groupMsgArray[j].MsgSeq < groupMsgs.min) {//记录最小的消息seq
          groupMsgMap[groupMsgArray[j].ToGroupId].min = groupMsgArray[j].MsgSeq;
        }
        if (groupMsgArray[j].MsgSeq > groupMsgs.max) {//记录最大的消息seq
          groupMsgMap[groupMsgArray[j].ToGroupId].max = groupMsgArray[j].MsgSeq;
        }
      }

      for (var groupId in groupMsgMap) {
        var tempCount = groupMsgMap[groupId].max - groupMsgMap[groupId].min + 1;//收到的新的群消息数
        var curMaxMsgSeq = myGroupMaxSeqs[groupId];//获取本地保存的群最大消息seq
        if (curMaxMsgSeq) {//存在这个群的最大消息seq
          //高并发情况下，长轮询可能存在丢消息，这时需要客户端通过拉取群消息接口补齐下
          //1、如果收到的新消息最小seq比当前最大群消息seq大于1，则表示收到的群消息发生跳跃，需要补齐
          //2、收到的新群消息seq存在不连续情况，也需要补齐
          if (groupMsgMap[groupId].min - curMaxMsgSeq > 1 || groupMsgMap[groupId].msgs.length < tempCount) {
            //发起一个拉群群消息请求
            log.warn("发起一次补齐群消息请求,curMaxMsgSeq=" + curMaxMsgSeq + ", minMsgSeq=" + groupMsgMap[groupId].min + ", maxMsgSeq=" + groupMsgMap[groupId].max + ", msgs.length=" + groupMsgMap[groupId].msgs.length + ", tempCount=" + tempCount);
            getLostGroupMsgs(groupId, groupMsgMap[groupId].max, groupMsgMap[groupId].max - curMaxMsgSeq);
            //更新myGroupMaxSeqs中的群最大seq
            updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
          } else {
            new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
          }
        } else {//不存在该群的最大消息seq
          log.warn("不存在该群的最大消息seq，群id=" + groupId);
          //高并发情况下，长轮询可能存在丢消息，这时需要客户端通过拉取群消息接口补齐下
          //1、收到的新群消息seq存在不连续情况，也需要补齐
          if (groupMsgMap[groupId].msgs.length < tempCount) {
            //发起一个拉群群消息请求
            log.warn("发起一次补齐群消息请求,minMsgSeq=" + groupMsgMap[groupId].min + ", maxMsgSeq=" + groupMsgMap[groupId].max + ", msgs.length=" + groupMsgMap[groupId].msgs.length + ", tempCount=" + tempCount);
            getLostGroupMsgs(groupId, groupMsgMap[groupId].max, tempCount);
            //更新myGroupMaxSeqs中的群最大seq
            updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
          } else {
            new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
          }
        }
      }
      if (new_group_msgs.length) {
        MsgStore.updateTimeline();
      }
      if (onMsgCallback && new_group_msgs.length) onMsgCallback(new_group_msgs);

    };

    //处理新的群提示消息
    var handlerGroupTips = function (groupTips) {
      var new_group_msgs = [];
      for (var o in groupTips) {
        var groupTip = groupTips[o];
        //添加event字段
        groupTip.Event = LONG_POLLINNG_EVENT_TYPE.GROUP_TIP;
        //更新群消息通知seq
        if (groupTip.NoticeSeq > noticeSeq) {
          noticeSeq = groupTip.NoticeSeq;
        }
        var msg = handlerGroupMsg(groupTip, false, true);
        if (msg) {
          new_group_msgs.push(msg);
        }
      }
      if (new_group_msgs.length) {
        MsgStore.updateTimeline();
      }
      if (onMsgCallback && new_group_msgs.length) onMsgCallback(new_group_msgs);
    };

    //处理新的群系统消息
    //isNeedValidRepeatMsg 是否需要判重
    var handlerGroupSystemMsgs = function (groupSystemMsgs, isNeedValidRepeatMsg) {
      for (var k in groupSystemMsgs) {
        var groupTip = groupSystemMsgs[k];
        var groupReportTypeMsg = groupTip.MsgBody;
        var reportType = groupReportTypeMsg.ReportType;
        //当长轮询返回的群系统消息，才需要更新群消息通知seq
        if (isNeedValidRepeatMsg == false && groupTip.NoticeSeq && groupTip.NoticeSeq > noticeSeq) {
          noticeSeq = groupTip.NoticeSeq;
        }
        var toAccount = groupTip.GroupInfo.To_Account;
        //过滤本不应该给自己的系统消息
        /*if (!toAccount || toAccount != ctx.identifier) {
         log.error("收到本不应该给自己的系统消息: To_Account=" + toAccount);
         continue;
         }*/
        if (isNeedValidRepeatMsg) {
          //var key=groupTip.ToGroupId+"_"+reportType+"_"+groupTip.MsgTimeStamp+"_"+groupReportTypeMsg.Operator_Account;
          var key = groupTip.ToGroupId + "_" + reportType + "_" + groupReportTypeMsg.Operator_Account;
          var isExist = groupSystemMsgsCache[key];
          if (isExist) {
            log.warn("收到重复的群系统消息：key=" + key);
            continue;
          }
          groupSystemMsgsCache[key] = true;
        }

        var notify = {
          "SrcFlag": 0,
          "ReportType": reportType,
          "GroupId": groupTip.ToGroupId,
          "GroupName": groupTip.GroupInfo.GroupName,
          "Operator_Account": groupReportTypeMsg.Operator_Account,
          "MsgTime": groupTip.MsgTimeStamp,
          "groupReportTypeMsg": groupReportTypeMsg
        };
        switch (reportType) {
          case GROUP_SYSTEM_TYPE.JOIN_GROUP_REQUEST://申请加群(只有管理员会接收到)
            notify["RemarkInfo"] = groupReportTypeMsg.RemarkInfo;
            notify["MsgKey"] = groupReportTypeMsg.MsgKey;
            notify["Authentication"] = groupReportTypeMsg.Authentication;
            notify["UserDefinedField"] = groupTip.UserDefinedField;
            notify["From_Account"] = groupTip.From_Account;
            notify["MsgSeq"] = groupTip.ClientSeq;
            notify["MsgRandom"] = groupTip.MsgRandom;
            break;
          case GROUP_SYSTEM_TYPE.JOIN_GROUP_ACCEPT://申请加群被同意(只有申请人自己接收到)
          case GROUP_SYSTEM_TYPE.JOIN_GROUP_REFUSE://申请加群被拒绝(只有申请人自己接收到)
            notify["RemarkInfo"] = groupReportTypeMsg.RemarkInfo;
            break;
          case GROUP_SYSTEM_TYPE.KICK://被管理员踢出群(只有被踢者接收到)
          case GROUP_SYSTEM_TYPE.DESTORY://群被解散(全员接收)
          case GROUP_SYSTEM_TYPE.CREATE://创建群(创建者接收, 不展示)
          case GROUP_SYSTEM_TYPE.INVITED_JOIN_GROUP_REQUEST://邀请加群(被邀请者接收)
          case GROUP_SYSTEM_TYPE.QUIT://主动退群(主动退出者接收, 不展示)
          case GROUP_SYSTEM_TYPE.SET_ADMIN://群设置管理员(被设置者接收)
          case GROUP_SYSTEM_TYPE.CANCEL_ADMIN://取消管理员(被取消者接收)
          case GROUP_SYSTEM_TYPE.REVOKE://群已被回收(全员接收, 不展示)
            break;
          case GROUP_SYSTEM_TYPE.READED://群消息已读同步
            // console.debug("已读同步....");
            // console.debug('reportType',reportType);
            break;
          case GROUP_SYSTEM_TYPE.CUSTOM://用户自定义通知(默认全员接收)
            notify["MsgSeq"] = groupTip.MsgSeq;
            notify["UserDefinedField"] = groupReportTypeMsg.UserDefinedField;
            break;
          default:
            log.error("未知群系统消息类型：reportType=" + reportType);
            break;
        }

        if (isNeedValidRepeatMsg) {
          if (reportType == GROUP_SYSTEM_TYPE.JOIN_GROUP_REQUEST) {
            //回调
            if (onGroupSystemNotifyCallbacks[reportType]) onGroupSystemNotifyCallbacks[reportType](notify);
          }
        } else {
          //回调
          if (onGroupSystemNotifyCallbacks[reportType]) onGroupSystemNotifyCallbacks[reportType](notify);
        }
      }//loop
    };


    //处理新的好友系统通知
    //isNeedValidRepeatMsg 是否需要判重
    var handlerFriendSystemNotices = function (friendSystemNotices, isNeedValidRepeatMsg) {
      var friendNotice, type, notify;
      for (var k in friendSystemNotices) {
        friendNotice = friendSystemNotices[k];
        type = friendNotice.PushType;
        //当长轮询返回的群系统消息，才需要更新通知seq
        if (isNeedValidRepeatMsg == false && friendNotice.NoticeSeq && friendNotice.NoticeSeq > noticeSeq) {
          noticeSeq = friendNotice.NoticeSeq;
        }
        notify = { 'Type': type };
        switch (type) {
          case FRIEND_NOTICE_TYPE.FRIEND_ADD://好友表增加
            notify["Accounts"] = friendNotice.FriendAdd_Account;
            break;
          case FRIEND_NOTICE_TYPE.FRIEND_DELETE://好友表删除
            notify["Accounts"] = friendNotice.FriendDel_Account;
            break;
          case FRIEND_NOTICE_TYPE.PENDENCY_ADD://未决增加
            notify["PendencyList"] = friendNotice.PendencyAdd;
            break;
          case FRIEND_NOTICE_TYPE.PENDENCY_DELETE://未决删除
            notify["Accounts"] = friendNotice.FrienPencydDel_Account;
            break;
          case FRIEND_NOTICE_TYPE.BLACK_LIST_ADD://黑名单增加
            notify["Accounts"] = friendNotice.BlackListAdd_Account;
            break;
          case FRIEND_NOTICE_TYPE.BLACK_LIST_DELETE://黑名单删除
            notify["Accounts"] = friendNotice.BlackListDel_Account;
            break;
          /*case FRIEND_NOTICE_TYPE.PENDENCY_REPORT://未决已读上报

           break;
           case FRIEND_NOTICE_TYPE.FRIEND_UPDATE://好友数据更新

           break;
           */
          default:
            log.error("未知好友系统通知类型：friendNotice=" + JSON.stringify(friendNotice));
            break;
        }

        if (isNeedValidRepeatMsg) {
          if (type == FRIEND_NOTICE_TYPE.PENDENCY_ADD) {
            //回调
            if (onFriendSystemNotifyCallbacks[type]) onFriendSystemNotifyCallbacks[type](notify);
          }
        } else {
          //回调
          if (onFriendSystemNotifyCallbacks[type]) onFriendSystemNotifyCallbacks[type](notify);
        }
      }//loop
    };

    //处理新的资料系统通知
    //isNeedValidRepeatMsg 是否需要判重
    var handlerProfileSystemNotices = function (profileSystemNotices, isNeedValidRepeatMsg) {
      var profileNotice, type, notify;
      for (var k in profileSystemNotices) {
        profileNotice = profileSystemNotices[k];
        type = profileNotice.PushType;
        //当长轮询返回的群系统消息，才需要更新通知seq
        if (isNeedValidRepeatMsg == false && profileNotice.NoticeSeq && profileNotice.NoticeSeq > noticeSeq) {
          noticeSeq = profileNotice.NoticeSeq;
        }
        notify = { 'Type': type };
        switch (type) {
          case PROFILE_NOTICE_TYPE.PROFILE_MODIFY://资料修改
            notify["Profile_Account"] = profileNotice.Profile_Account;
            notify["ProfileList"] = profileNotice.ProfileList;
            break;
          default:
            log.error("未知资料系统通知类型：profileNotice=" + JSON.stringify(profileNotice));
            break;
        }

        if (isNeedValidRepeatMsg) {
          if (type == PROFILE_NOTICE_TYPE.PROFILE_MODIFY) {
            //回调
            if (onProfileSystemNotifyCallbacks[type]) onProfileSystemNotifyCallbacks[type](notify);
          }
        } else {
          //回调
          if (onProfileSystemNotifyCallbacks[type]) onProfileSystemNotifyCallbacks[type](notify);
        }
      }//loop
    };

    //处理新的群系统消息(用于直播大群长轮询)
    var handlerGroupSystemMsg = function (groupTip) {
      var groupReportTypeMsg = groupTip.MsgBody;
      var reportType = groupReportTypeMsg.ReportType;
      var toAccount = groupTip.GroupInfo.To_Account;
      //过滤本不应该给自己的系统消息
      //if(!toAccount || toAccount!=ctx.identifier){
      //    log.error("收到本不应该给自己的系统消息: To_Account="+toAccount);
      //    continue;
      //}
      var notify = {
        "SrcFlag": 1,
        "ReportType": reportType,
        "GroupId": groupTip.ToGroupId,
        "GroupName": groupTip.GroupInfo.GroupName,
        "Operator_Account": groupReportTypeMsg.Operator_Account,
        "MsgTime": groupTip.MsgTimeStamp
      };
      switch (reportType) {
        case GROUP_SYSTEM_TYPE.JOIN_GROUP_REQUEST://申请加群(只有管理员会接收到)
          notify["RemarkInfo"] = groupReportTypeMsg.RemarkInfo;
          notify["MsgKey"] = groupReportTypeMsg.MsgKey;
          notify["Authentication"] = groupReportTypeMsg.Authentication;
          notify["UserDefinedField"] = groupTip.UserDefinedField;
          notify["From_Account"] = groupTip.From_Account;
          notify["MsgSeq"] = groupTip.ClientSeq;
          notify["MsgRandom"] = groupTip.MsgRandom;
          break;
        case GROUP_SYSTEM_TYPE.JOIN_GROUP_ACCEPT://申请加群被同意(只有申请人自己接收到)
        case GROUP_SYSTEM_TYPE.JOIN_GROUP_REFUSE://申请加群被拒绝(只有申请人自己接收到)
          notify["RemarkInfo"] = groupReportTypeMsg.RemarkInfo;
          break;
        case GROUP_SYSTEM_TYPE.KICK://被管理员踢出群(只有被踢者接收到)
        case GROUP_SYSTEM_TYPE.DESTORY://群被解散(全员接收)
        case GROUP_SYSTEM_TYPE.CREATE://创建群(创建者接收, 不展示)
        case GROUP_SYSTEM_TYPE.INVITED_JOIN_GROUP_REQUEST://邀请加群(被邀请者接收)
        case GROUP_SYSTEM_TYPE.QUIT://主动退群(主动退出者接收, 不展示)
        case GROUP_SYSTEM_TYPE.SET_ADMIN://群设置管理员(被设置者接收)
        case GROUP_SYSTEM_TYPE.CANCEL_ADMIN://取消管理员(被取消者接收)
        case GROUP_SYSTEM_TYPE.REVOKE://群已被回收(全员接收, 不展示)
          break;
        case GROUP_SYSTEM_TYPE.CUSTOM://用户自定义通知(默认全员接收)
          notify["MsgSeq"] = groupTip.MsgSeq;
          notify["UserDefinedField"] = groupReportTypeMsg.UserDefinedField;
          break;
        default:
          log.error("未知群系统消息类型：reportType=" + reportType);
          break;
      }
      //回调
      if (onGroupSystemNotifyCallbacks[reportType]) onGroupSystemNotifyCallbacks[reportType](notify);

    };

    //处理C2C EVENT 消息通道Array
    var handlerC2cNotifyMsgArray = function (arr) {
      for (var i = 0, l = arr.length; i < l; i++) {
        handlerC2cEventMsg(arr[i]);
      }
    }

    //处理C2C EVENT 消息通道Item
    var handlerC2cEventMsg = function (notify) {
      var subType = notify.SubMsgType;
      switch (subType) {
        case C2C_EVENT_SUB_TYPE.READED://已读通知
          break;
        default:
          log.error("未知C2c系统消息：reportType=" + reportType);
          break;
      }
      // stopPolling = true;
      //回调onMsgReadCallback
      if (onC2cEventCallbacks[subType]) onC2cEventCallbacks[subType](notify);

    };

    //长轮询
    this.longPolling = function (cbOk, cbErr) {


      var opts = {
        'Timeout': longPollingDefaultTimeOut / 1000,
        'Cookie': {
          'NotifySeq': notifySeq,
          'NoticeSeq': noticeSeq
        }
      };
      if (LongPollingId) {
        opts.Cookie.LongPollingId = LongPollingId;
        doPolling();
      } else {
        proto_getLongPollingId({}, function (resp) {
          LongPollingId = opts.Cookie.LongPollingId = resp.LongPollingId;
          //根据回包设置超时时间，超时时长不能>60秒，因为webkit手机端的最长超时时间不能大于60s
          longPollingDefaultTimeOut = resp.Timeout > 60 ? longPollingDefaultTimeOut : resp.Timeout * 1000;
          doPolling();
        });
      }

      function doPolling() {
        proto_longPolling(opts, function (resp) {
          for (var i in resp.EventArray) {
            var e = resp.EventArray[i];
            switch (e.Event) {
              case LONG_POLLINNG_EVENT_TYPE.C2C://c2c消息通知
                //更新C2C消息通知seq
                notifySeq = e.NotifySeq;
                log.warn("longpolling: received new c2c msg");
                //获取新消息
                MsgManager.syncMsgs();
                break;
              case LONG_POLLINNG_EVENT_TYPE.GROUP_COMMON://普通群消息通知
                log.warn("longpolling: received new group msgs");
                handlerOrdinaryAndTipGroupMsgs(e.Event, e.GroupMsgArray);
                break;
              case LONG_POLLINNG_EVENT_TYPE.GROUP_TIP://（全员广播）群提示消息
                log.warn("longpolling: received new group tips");
                handlerOrdinaryAndTipGroupMsgs(e.Event, e.GroupTips);
                break;
              case LONG_POLLINNG_EVENT_TYPE.GROUP_SYSTEM://（多终端同步）群系统消息
                log.warn("longpolling: received new group system msgs");
                //false 表示 通过长轮询收到的群系统消息，可以不判重
                handlerGroupSystemMsgs(e.GroupTips, false);
                break;
              case LONG_POLLINNG_EVENT_TYPE.FRIEND_NOTICE://好友系统通知
                log.warn("longpolling: received new friend system notice");
                //false 表示 通过长轮询收到的好友系统通知，可以不判重
                handlerFriendSystemNotices(e.FriendListMod, false);
                break;
              case LONG_POLLINNG_EVENT_TYPE.PROFILE_NOTICE://资料系统通知
                log.warn("longpolling: received new profile system notice");
                //false 表示 通过长轮询收到的资料系统通知，可以不判重
                handlerProfileSystemNotices(e.ProfileDataMod, false);
                break;
              case LONG_POLLINNG_EVENT_TYPE.C2C_COMMON://c2c消息通知
                noticeSeq = e.C2cMsgArray[0].NoticeSeq;
                //更新C2C消息通知seq
                log.warn("longpolling: received new c2c_common msg", noticeSeq);
                handlerOrdinaryAndTipC2cMsgs(e.Event, e.C2cMsgArray);
                break;
              case LONG_POLLINNG_EVENT_TYPE.C2C_EVENT://c2c已读消息通知
                noticeSeq = e.C2cNotifyMsgArray[0].NoticeSeq;
                log.warn("longpolling: received new c2c_event msg");
                handlerC2cNotifyMsgArray(e.C2cNotifyMsgArray);
                break;
              default:
                log.error("longpolling收到未知新消息类型: Event=" + e.Event);
                break;
            }
          }
          var successInfo = {
            'ActionStatus': ACTION_STATUS.OK,
            'ErrorCode': 0
          };
          updatecLongPollingStatus(successInfo);
        }, function (err) {
          //log.error(err);
          updatecLongPollingStatus(err);
          if (cbErr) cbErr(err);
        });
      }
    };


    //大群 长轮询
    this.bigGroupLongPolling = function (cbOk, cbErr) {

      var opts = {
        'StartSeq': bigGroupLongPollingStartSeq,//请求拉消息的起始seq
        'HoldTime': bigGroupLongPollingHoldTime,//客户端长轮询的超时时间，单位是秒
        'Key': bigGroupLongPollingKey//客户端加入群组后收到的的Key
      };

      proto_bigGroupLongPolling(opts, function (resp) {

        var msgObjList = [];
        bigGroupLongPollingStartSeq = resp.NextSeq;
        bigGroupLongPollingHoldTime = resp.HoldTime;
        bigGroupLongPollingKey = resp.Key;

        if (resp.RspMsgList && resp.RspMsgList.length > 0) {
          var msgCount = 0, msgInfo, event, msg;
          for (var i = resp.RspMsgList.length - 1; i >= 0; i--) {
            msgInfo = resp.RspMsgList[i];
            //如果是已经删除的消息或者发送者帐号为空或者消息内容为空
            //IsPlaceMsg=1
            if (msgInfo.IsPlaceMsg || !msgInfo.From_Account || !msgInfo.MsgBody || msgInfo.MsgBody.length == 0) {
              continue;
            }

            event = msgInfo.Event;//群消息类型
            switch (event) {
              case LONG_POLLINNG_EVENT_TYPE.GROUP_COMMON://群普通消息
                log.info("bigGroupLongPolling: return new group msg");
                msg = handlerGroupMsg(msgInfo, false, false);
                msg && msgObjList.push(msg);
                msgCount = msgCount + 1;
                break;
              case LONG_POLLINNG_EVENT_TYPE.GROUP_TIP://群提示消息
              case LONG_POLLINNG_EVENT_TYPE.GROUP_TIP2://群提示消息
                log.info("bigGroupLongPolling: return new group tip");
                msg = handlerGroupMsg(msgInfo, false, false);
                msg && msgObjList.push(msg);
                //msgCount=msgCount+1;
                break;
              case LONG_POLLINNG_EVENT_TYPE.GROUP_SYSTEM://群系统消息
                log.info("bigGroupLongPolling: new group system msg");
                handlerGroupSystemMsg(msgInfo);
                break;
              default:
                log.error("bigGroupLongPolling收到未知新消息类型: Event=" + event);
                break;
            }
          } // for loop
          if (msgCount > 0) {
            MsgManager.setBigGroupLongPollingMsgMap(msgInfo.ToGroupId, msgCount);//
            log.warn("current bigGroupLongPollingMsgMap: " + JSON.stringify(bigGroupLongPollingMsgMap));
          }
        }
        curBigGroupLongPollingRetErrorCount = 0;
        //返回连接状态
        var successInfo = {
          'ActionStatus': ACTION_STATUS.OK,
          'ErrorCode': CONNECTION_STATUS.ON,
          'ErrorInfo': 'connection is ok...'
        };
        ConnManager.callBack(successInfo);

        if (cbOk) cbOk(msgObjList);
        else if (onBigGroupMsgCallback) onBigGroupMsgCallback(msgObjList);//返回新消息

        //重新启动长轮询
        bigGroupLongPollingOn && MsgManager.bigGroupLongPolling();

      }, function (err) {
        //
        if (err.ErrorCode != longPollingTimeOutErrorCode) {
          log.error(err.ErrorInfo);
          //记录长轮询返回错误次数
          curBigGroupLongPollingRetErrorCount++;
        }
        if (err.ErrorCode == longPollingKickedErrorCode) {
          //登出
          log.error("多实例登录，被kick");

          if (onKickedEventCall) { onKickedEventCall(); }
          /*    return;
          proto_logout(function(){
              if (onKickedEventCall) {onKickedEventCall();}
          });*/
        }
        //累计超过一定次数，不再发起长轮询请求
        if (curBigGroupLongPollingRetErrorCount < LONG_POLLING_MAX_RET_ERROR_COUNT) {
          bigGroupLongPollingOn && MsgManager.bigGroupLongPolling();
        } else {
          var errInfo = {
            'ActionStatus': ACTION_STATUS.FAIL,
            'ErrorCode': CONNECTION_STATUS.OFF,
            'ErrorInfo': 'connection is off'
          };
          ConnManager.callBack(errInfo);
        }
        if (cbErr) cbErr(err);

      }, bigGroupLongPollingHoldTime * 1000);
    };

    //更新连接状态
    var updatecLongPollingStatus = function (errObj) {
      if (errObj.ErrorCode == 0 || errObj.ErrorCode == longPollingTimeOutErrorCode) {
        curLongPollingRetErrorCount = 0;
        longPollingOffCallbackFlag = false;
        var errorInfo;
        var isNeedCallback = false;
        switch (curLongPollingStatus) {
          case CONNECTION_STATUS.INIT:
            isNeedCallback = true;
            curLongPollingStatus = CONNECTION_STATUS.ON;
            errorInfo = "create connection successfully(INIT->ON)";
            break;
          case CONNECTION_STATUS.ON:
            errorInfo = "connection is on...(ON->ON)";
            break;
          case CONNECTION_STATUS.RECONNECT:
            curLongPollingStatus = CONNECTION_STATUS.ON;
            errorInfo = "connection is on...(RECONNECT->ON)";
            break;
          case CONNECTION_STATUS.OFF:
            isNeedCallback = true;
            curLongPollingStatus = CONNECTION_STATUS.RECONNECT;
            errorInfo = "reconnect successfully(OFF->RECONNECT)";
            break;
        }
        var successInfo = {
          'ActionStatus': ACTION_STATUS.OK,
          'ErrorCode': curLongPollingStatus,
          'ErrorInfo': errorInfo
        };
        isNeedCallback && ConnManager.callBack(successInfo);
        longPollingOn && MsgManager.longPolling();
      } else if (errObj.ErrorCode == longPollingKickedErrorCode) {
        //登出
        log.error("多实例登录，被kick");
        if (onKickedEventCall) { onKickedEventCall(); }
        //     return;
        // proto_logout(function(){
        //     if (onKickedEventCall) {onKickedEventCall();}
        // });
      } else {
        //记录长轮询返回解析json错误次数
        curLongPollingRetErrorCount++;
        log.warn("longPolling接口第" + curLongPollingRetErrorCount + "次报错: " + errObj.ErrorInfo);
        //累计超过一定次数
        if (curLongPollingRetErrorCount <= LONG_POLLING_MAX_RET_ERROR_COUNT) {
          setTimeout(startNextLongPolling, 100);//
        } else {
          curLongPollingStatus = CONNECTION_STATUS.OFF;
          var errInfo = {
            'ActionStatus': ACTION_STATUS.FAIL,
            'ErrorCode': CONNECTION_STATUS.OFF,
            'ErrorInfo': 'connection is off'
          };
          longPollingOffCallbackFlag == false && ConnManager.callBack(errInfo);
          longPollingOffCallbackFlag = true;
          log.warn(longPollingIntervalTime + "毫秒之后,SDK会发起新的longPolling请求...");
          setTimeout(startNextLongPolling, longPollingIntervalTime);//长轮询接口报错次数达到一定值，每间隔5s发起新的长轮询
        }
      }
    };

    //处理收到的普通C2C消息
    var handlerOrdinaryAndTipC2cMsgs = function (eventType, C2cMsgArray) {
      //处理c2c消息
      var notifyInfo = [];
      msgInfos = C2cMsgArray;//返回的消息列表
      // MsgStore.cookie = resp.Cookie;//cookies，记录当前读到的最新消息位置

      for (var i in msgInfos) {
        var msgInfo = msgInfos[i];
        var isSendMsg, id, headUrl;
        if (msgInfo.From_Account == ctx.identifier) {//当前用户发送的消息
          isSendMsg = true;
          id = msgInfo.To_Account;//读取接收者信息
          headUrl = '';
        } else {//当前用户收到的消息
          isSendMsg = false;
          id = msgInfo.From_Account;//读取发送者信息
          headUrl = '';
        }
        var sess = MsgStore.sessByTypeId(SESSION_TYPE.C2C, id);
        if (!sess) {
          sess = new Session(SESSION_TYPE.C2C, id, id, headUrl, 0, 0);
        }
        var msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account);
        var msgBody = null;
        var msgContent = null;
        var msgType = null;
        for (var mi in msgInfo.MsgBody) {
          msgBody = msgInfo.MsgBody[mi];
          msgType = msgBody.MsgType;
          switch (msgType) {
            case MSG_ELEMENT_TYPE.TEXT:
              msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
              break;
            case MSG_ELEMENT_TYPE.FACE:
              msgContent = new Msg.Elem.Face(
                msgBody.MsgContent.Index,
                msgBody.MsgContent.Data
              );
              break;
            case MSG_ELEMENT_TYPE.IMAGE:
              msgContent = new Msg.Elem.Images(
                msgBody.MsgContent.UUID
              );
              for (var j in msgBody.MsgContent.ImageInfoArray) {
                var tempImg = msgBody.MsgContent.ImageInfoArray[j];
                msgContent.addImage(
                  new Msg.Elem.Images.Image(
                    tempImg.Type,
                    tempImg.Size,
                    tempImg.Width,
                    tempImg.Height,
                    tempImg.URL
                  )
                );
              }
              break;
            case MSG_ELEMENT_TYPE.SOUND:
              if (msgBody.MsgContent) {
                msgContent = new Msg.Elem.Sound(
                  msgBody.MsgContent.UUID,
                  msgBody.MsgContent.Second,
                  msgBody.MsgContent.Size,
                  msgInfo.From_Account,
                  msgInfo.To_Account,
                  msgBody.MsgContent.Download_Flag,
                  SESSION_TYPE.C2C
                );
              } else {
                msgType = MSG_ELEMENT_TYPE.TEXT;
                msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
              }
              break;
            case MSG_ELEMENT_TYPE.LOCATION:
              msgContent = new Msg.Elem.Location(
                msgBody.MsgContent.Longitude,
                msgBody.MsgContent.Latitude,
                msgBody.MsgContent.Desc
              );
              break;
            case MSG_ELEMENT_TYPE.FILE:
            case MSG_ELEMENT_TYPE.FILE + " ":
              msgType = MSG_ELEMENT_TYPE.FILE;
              if (msgBody.MsgContent) {
                msgContent = new Msg.Elem.File(
                  msgBody.MsgContent.UUID,
                  msgBody.MsgContent.FileName,
                  msgBody.MsgContent.FileSize,
                  msgInfo.From_Account,
                  msgInfo.To_Account,
                  msgBody.MsgContent.Download_Flag,
                  SESSION_TYPE.C2C
                );
              } else {
                msgType = MSG_ELEMENT_TYPE.TEXT;
                msgContent = new Msg.Elem.Text('[文件消息下载地址解析出错]');
              }
              break;
            case MSG_ELEMENT_TYPE.CUSTOM:
              try {
                var data = JSON.parse(msgBody.MsgContent.Data);
                if (data && data.userAction && data.userAction == FRIEND_WRITE_MSG_ACTION.ING) {//过滤安卓或ios的正在输入自定义消息
                  continue;
                }
              } catch (e) {
              }

              msgType = MSG_ELEMENT_TYPE.CUSTOM;
              msgContent = new Msg.Elem.Custom(
                msgBody.MsgContent.Data,
                msgBody.MsgContent.Desc,
                msgBody.MsgContent.Ext
              );
              break;
            default:
              msgType = MSG_ELEMENT_TYPE.TEXT;
              msgContent = new Msg.Elem.Text('web端暂不支持' + msgBody.MsgType + '消息');
              break;
          }
          msg.elems.push(new Msg.Elem(msgType, msgContent));
        }

        if (msg.elems.length > 0 && MsgStore.addMsg(msg)) {
          notifyInfo.push(msg);
        }
      } // for loop
      if (notifyInfo.length > 0)
        MsgStore.updateTimeline();
      if (notifyInfo.length > 0) {
        if (onMsgCallback) onMsgCallback(notifyInfo);
      }
    };

    //发起新的长轮询请求
    var startNextLongPolling = function () {
      longPollingOn && MsgManager.longPolling();
    };

    //处理未决的加群申请消息列表
    var handlerApplyJoinGroupSystemMsgs = function (eventArray) {
      for (var i in eventArray) {
        var e = eventArray[i];
        switch (e.Event) {
          case LONG_POLLINNG_EVENT_TYPE.GROUP_SYSTEM://（多终端同步）群系统消息
            log.warn("handlerApplyJoinGroupSystemMsgs： handler new group system msg");
            //true 表示 解决加群申请通知存在重复的问题（已处理的通知，下次登录还会拉到），需要判重
            handlerGroupSystemMsgs(e.GroupTips, true);
            break;
          default:
            log.error("syncMsgs收到未知的群系统消息类型: Event=" + e.Event);
            break;
        }
      }
    };

    //拉取c2c消息(包含加群未决消息，需要处理)
    this.syncMsgs = function (cbOk, cbErr) {
      var notifyInfo = [];
      var msgInfos = [];
      //读取C2C消息
      proto_getMsgs(MsgStore.cookie, MsgStore.syncFlag, function (resp) {
        //拉取完毕
        if (resp.SyncFlag == 2) {
          MsgStore.syncFlag = 0;
        }
        //处理c2c消息
        msgInfos = resp.MsgList;//返回的消息列表
        MsgStore.cookie = resp.Cookie;//cookies，记录当前读到的最新消息位置

        for (var i in msgInfos) {
          var msgInfo = msgInfos[i];
          var isSendMsg, id, headUrl;
          if (msgInfo.From_Account == ctx.identifier) {//当前用户发送的消息
            isSendMsg = true;
            id = msgInfo.To_Account;//读取接收者信息
            headUrl = '';
          } else {//当前用户收到的消息
            isSendMsg = false;
            id = msgInfo.From_Account;//读取发送者信息
            headUrl = '';
          }
          var sess = MsgStore.sessByTypeId(SESSION_TYPE.C2C, id);
          if (!sess) {
            sess = new Session(SESSION_TYPE.C2C, id, id, headUrl, 0, 0);
          }
          var msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account);
          var msgBody = null;
          var msgContent = null;
          var msgType = null;
          for (var mi in msgInfo.MsgBody) {
            msgBody = msgInfo.MsgBody[mi];
            msgType = msgBody.MsgType;
            switch (msgType) {
              case MSG_ELEMENT_TYPE.TEXT:
                msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
                break;
              case MSG_ELEMENT_TYPE.FACE:
                msgContent = new Msg.Elem.Face(
                  msgBody.MsgContent.Index,
                  msgBody.MsgContent.Data
                );
                break;
              case MSG_ELEMENT_TYPE.IMAGE:
                msgContent = new Msg.Elem.Images(
                  msgBody.MsgContent.UUID
                );
                for (var j in msgBody.MsgContent.ImageInfoArray) {
                  var tempImg = msgBody.MsgContent.ImageInfoArray[j];
                  msgContent.addImage(
                    new Msg.Elem.Images.Image(
                      tempImg.Type,
                      tempImg.Size,
                      tempImg.Width,
                      tempImg.Height,
                      tempImg.URL
                    )
                  );
                }
                break;
              case MSG_ELEMENT_TYPE.SOUND:
                // var soundUrl = getSoundDownUrl(msgBody.MsgContent.UUID, msgInfo.From_Account);
                if (msgBody.MsgContent) {
                  msgContent = new Msg.Elem.Sound(
                    msgBody.MsgContent.UUID,
                    msgBody.MsgContent.Second,
                    msgBody.MsgContent.Size,
                    msgInfo.From_Account,
                    msgInfo.To_Account,
                    msgBody.MsgContent.Download_Flag,
                    SESSION_TYPE.C2C
                  );
                } else {
                  msgType = MSG_ELEMENT_TYPE.TEXT;
                  msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
                }
                break;
              case MSG_ELEMENT_TYPE.LOCATION:
                msgContent = new Msg.Elem.Location(
                  msgBody.MsgContent.Longitude,
                  msgBody.MsgContent.Latitude,
                  msgBody.MsgContent.Desc
                );
                break;
              case MSG_ELEMENT_TYPE.FILE:
              case MSG_ELEMENT_TYPE.FILE + " ":
                msgType = MSG_ELEMENT_TYPE.FILE;
                // var fileUrl = getFileDownUrl(msgBody.MsgContent.UUID, msgInfo.From_Account, msgBody.MsgContent.FileName);
                if (msgBody.MsgContent) {
                  msgContent = new Msg.Elem.File(
                    msgBody.MsgContent.UUID,
                    msgBody.MsgContent.FileName,
                    msgBody.MsgContent.FileSize,
                    msgInfo.From_Account,
                    msgInfo.To_Account,
                    msgBody.MsgContent.Download_Flag,
                    SESSION_TYPE.C2C
                  );
                } else {
                  msgType = MSG_ELEMENT_TYPE.TEXT;
                  msgContent = new Msg.Elem.Text('[文件消息下载地址解析出错]');
                }
                break;
              case MSG_ELEMENT_TYPE.CUSTOM:
                try {
                  var data = JSON.parse(msgBody.MsgContent.Data);
                  if (data && data.userAction && data.userAction == FRIEND_WRITE_MSG_ACTION.ING) {//过滤安卓或ios的正在输入自定义消息
                    continue;
                  }
                } catch (e) {
                }

                msgType = MSG_ELEMENT_TYPE.CUSTOM;
                msgContent = new Msg.Elem.Custom(
                  msgBody.MsgContent.Data,
                  msgBody.MsgContent.Desc,
                  msgBody.MsgContent.Ext
                );
                break;
              default:
                msgType = MSG_ELEMENT_TYPE.TEXT;
                msgContent = new Msg.Elem.Text('web端暂不支持' + msgBody.MsgType + '消息');
                break;
            }
            msg.elems.push(new Msg.Elem(msgType, msgContent));
          }

          if (msg.elems.length > 0 && MsgStore.addMsg(msg)) {
            notifyInfo.push(msg);
          }
        } // for loop

        //处理加群未决申请消息
        handlerApplyJoinGroupSystemMsgs(resp.EventArray);

        if (notifyInfo.length > 0)
          MsgStore.updateTimeline();
        if (cbOk) cbOk(notifyInfo);
        else if (notifyInfo.length > 0) {
          if (onMsgCallback) onMsgCallback(notifyInfo);
        }

      }, function (err) {
        log.error("getMsgs failed:" + err.ErrorInfo);
        if (cbErr) cbErr(err);
      });
    };


    //拉取C2C漫游消息
    this.getC2CHistoryMsgs = function (options, cbOk, cbErr) {

      if (!options.Peer_Account) {
        if (cbErr) {
          cbErr(tool.getReturnError("Peer_Account is empty", -13));
          return;
        }
      }
      if (!options.MaxCnt) {
        options.MaxCnt = 15;
      }
      if (options.MaxCnt <= 0) {
        if (cbErr) {
          cbErr(tool.getReturnError("MaxCnt should be greater than 0", -14));
          return;
        }
      }
      if (options.MaxCnt > 15) {
        if (cbErr) {
          cbErr(tool.getReturnError("MaxCnt can not be greater than 15", -15));
          return;
        }
        return;
      }
      if (options.MsgKey == null || options.MsgKey === undefined) {
        options.MsgKey = '';
      }
      var opts = {
        'Peer_Account': options.Peer_Account,
        'MaxCnt': options.MaxCnt,
        'LastMsgTime': options.LastMsgTime,
        'MsgKey': options.MsgKey
      };
      //读取c2c漫游消息
      proto_getC2CHistoryMsgs(opts, function (resp) {
        var msgObjList = [];
        //处理c2c消息
        msgInfos = resp.MsgList;//返回的消息列表
        var sess = MsgStore.sessByTypeId(SESSION_TYPE.C2C, options.Peer_Account);
        if (!sess) {
          sess = new Session(SESSION_TYPE.C2C, options.Peer_Account, options.Peer_Account, '', 0, 0);
        }
        for (var i in msgInfos) {
          var msgInfo = msgInfos[i];
          var isSendMsg, id, headUrl;
          if (msgInfo.From_Account == ctx.identifier) {//当前用户发送的消息
            isSendMsg = true;
            id = msgInfo.To_Account;//读取接收者信息
            headUrl = '';
          } else {//当前用户收到的消息
            isSendMsg = false;
            id = msgInfo.From_Account;//读取发送者信息
            headUrl = '';
          }
          var msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account);
          var msgBody = null;
          var msgContent = null;
          var msgType = null;
          for (var mi in msgInfo.MsgBody) {
            msgBody = msgInfo.MsgBody[mi];
            msgType = msgBody.MsgType;
            switch (msgType) {
              case MSG_ELEMENT_TYPE.TEXT:
                msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
                break;
              case MSG_ELEMENT_TYPE.FACE:
                msgContent = new Msg.Elem.Face(
                  msgBody.MsgContent.Index,
                  msgBody.MsgContent.Data
                );
                break;
              case MSG_ELEMENT_TYPE.IMAGE:
                msgContent = new Msg.Elem.Images(
                  msgBody.MsgContent.UUID
                );
                for (var j in msgBody.MsgContent.ImageInfoArray) {
                  var tempImg = msgBody.MsgContent.ImageInfoArray[j];
                  msgContent.addImage(
                    new Msg.Elem.Images.Image(
                      tempImg.Type,
                      tempImg.Size,
                      tempImg.Width,
                      tempImg.Height,
                      tempImg.URL
                    )
                  );
                }
                break;
              case MSG_ELEMENT_TYPE.SOUND:

                // var soundUrl = getSoundDownUrl(msgBody.MsgContent.UUID, msgInfo.From_Account);

                if (msgBody.MsgContent) {
                  msgContent = new Msg.Elem.Sound(
                    msgBody.MsgContent.UUID,
                    msgBody.MsgContent.Second,
                    msgBody.MsgContent.Size,
                    msgInfo.From_Account,
                    msgInfo.To_Account,
                    msgBody.MsgContent.Download_Flag,
                    SESSION_TYPE.C2C
                  );
                } else {
                  msgType = MSG_ELEMENT_TYPE.TEXT;
                  msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
                }
                break;
              case MSG_ELEMENT_TYPE.LOCATION:
                msgContent = new Msg.Elem.Location(
                  msgBody.MsgContent.Longitude,
                  msgBody.MsgContent.Latitude,
                  msgBody.MsgContent.Desc
                );
                break;
              case MSG_ELEMENT_TYPE.FILE:
              case MSG_ELEMENT_TYPE.FILE + " ":
                msgType = MSG_ELEMENT_TYPE.FILE;
                // var fileUrl = getFileDownUrl(msgBody.MsgContent.UUID, msgInfo.From_Account, msgBody.MsgContent.FileName);

                if (msgBody.MsgContent) {
                  msgContent = new Msg.Elem.File(
                    msgBody.MsgContent.UUID,
                    msgBody.MsgContent.FileName,
                    msgBody.MsgContent.FileSize,
                    msgInfo.From_Account,
                    msgInfo.To_Account,
                    msgBody.MsgContent.Download_Flag,
                    SESSION_TYPE.C2C
                  );
                } else {
                  msgType = MSG_ELEMENT_TYPE.TEXT;
                  msgContent = new Msg.Elem.Text('[文件消息下载地址解析出错]');
                }
                break;
              case MSG_ELEMENT_TYPE.CUSTOM:
                msgType = MSG_ELEMENT_TYPE.CUSTOM;
                msgContent = new Msg.Elem.Custom(
                  msgBody.MsgContent.Data,
                  msgBody.MsgContent.Desc,
                  msgBody.MsgContent.Ext
                );

                break;
              default:
                msgType = MSG_ELEMENT_TYPE.TEXT;
                msgContent = new Msg.Elem.Text('web端暂不支持' + msgBody.MsgType + '消息');
                break;
            }
            msg.elems.push(new Msg.Elem(msgType, msgContent));
          }
          MsgStore.addMsg(msg);
          msgObjList.push(msg);
        } // for loop

        MsgStore.updateTimeline();
        if (cbOk) {

          var newResp = {
            'Complete': resp.Complete,
            'MsgCount': msgObjList.length,
            'LastMsgTime': resp.LastMsgTime,
            'MsgKey': resp.MsgKey,
            'MsgList': msgObjList
          };
          sess.isFinished(resp.Complete);
          cbOk(newResp);
        }

      }, function (err) {
        log.error("getC2CHistoryMsgs failed:" + err.ErrorInfo);
        if (cbErr) cbErr(err);
      });
    };

    //拉群历史消息
    //不传cbOk 和 cbErr，则会调用新消息回调函数
    this.syncGroupMsgs = function (options, cbOk, cbErr) {
      if (options.ReqMsgSeq <= 0) {
        if (cbErr) {
          var errInfo = "ReqMsgSeq must be greater than 0";
          var error = tool.getReturnError(errInfo, -16);
          cbErr(error);
        }
        return;
      }
      var opts = {
        'GroupId': options.GroupId,
        'ReqMsgSeq': options.ReqMsgSeq,
        'ReqMsgNumber': options.ReqMsgNumber
      };
      //读群漫游消息
      proto_getGroupMsgs(opts, function (resp) {
        var notifyInfo = [];
        var group_id = resp.GroupId;//返回的群id
        var msgInfos = resp.RspMsgList;//返回的消息列表
        var isFinished = resp.IsFinished;

        if (msgInfos == null || msgInfos === undefined) {
          if (cbOk) {
            cbOk([]);
          }
          return;
        }
        for (var i = msgInfos.length - 1; i >= 0; i--) {
          var msgInfo = msgInfos[i];
          //如果是已经删除的消息或者发送者帐号为空或者消息内容为空
          //IsPlaceMsg=1
          if (msgInfo.IsPlaceMsg || !msgInfo.From_Account || !msgInfo.MsgBody || msgInfo.MsgBody.length == 0) {
            continue;
          }
          var msg = handlerGroupMsg(msgInfo, true, true, isFinished);
          if (msg) {
            notifyInfo.push(msg);
          }
        } // for loop
        if (notifyInfo.length > 0)
          MsgStore.updateTimeline();
        if (cbOk) cbOk(notifyInfo);
        else if (notifyInfo.length > 0) {
          if (onMsgCallback) onMsgCallback(notifyInfo);
        }

      }, function (err) {
        log.error("getGroupMsgs failed:" + err.ErrorInfo);
        if (cbErr) cbErr(err);
      });
    };

    //处理群消息(普通消息+提示消息)
    //isSyncGroupMsgs 是否主动拉取群消息标志
    //isAddMsgFlag 是否需要保存到MsgStore，如果需要，这里会存在判重逻辑
    var handlerGroupMsg = function (msgInfo, isSyncGroupMsgs, isAddMsgFlag, isFinished) {
      if (msgInfo.IsPlaceMsg || !msgInfo.From_Account || !msgInfo.MsgBody || msgInfo.MsgBody.length == 0) {
        return null;
      }
      var isSendMsg, id, headUrl, fromAccountNick;
      var group_id = msgInfo.ToGroupId;
      var group_name = group_id;
      if (msgInfo.GroupInfo) {//取出群名称
        if (msgInfo.GroupInfo.GroupName) {
          group_name = msgInfo.GroupInfo.GroupName;
        }
      }
      //取出成员昵称
      fromAccountNick = msgInfo.From_Account;
      if (msgInfo.GroupInfo) {
        if (msgInfo.GroupInfo.From_AccountNick) {
          fromAccountNick = msgInfo.GroupInfo.From_AccountNick;
        }
      }
      if (msgInfo.From_Account == ctx.identifier) {//当前用户发送的消息
        isSendMsg = true;
        id = msgInfo.From_Account;//读取接收者信息
        headUrl = '';
      } else {//当前用户收到的消息
        isSendMsg = false;
        id = msgInfo.From_Account;//读取发送者信息
        headUrl = '';
      }
      var sess = MsgStore.sessByTypeId(SESSION_TYPE.GROUP, group_id);
      if (!sess) {
        sess = new Session(SESSION_TYPE.GROUP, group_id, group_name, headUrl, 0, 0);
      }
      if (typeof isFinished !== "undefined") {
        sess.isFinished(isFinished || 0);
      }
      var subType = GROUP_MSG_SUB_TYPE.COMMON;//消息类型
      //群提示消息,重新封装下
      if (LONG_POLLINNG_EVENT_TYPE.GROUP_TIP == msgInfo.Event || LONG_POLLINNG_EVENT_TYPE.GROUP_TIP2 == msgInfo.Event) {
        subType = GROUP_MSG_SUB_TYPE.TIP;
        var groupTip = msgInfo.MsgBody;
        msgInfo.MsgBody = [];
        msgInfo.MsgBody.push({
          "MsgType": MSG_ELEMENT_TYPE.GROUP_TIP,
          "MsgContent": groupTip
        }
        );
      } else if (msgInfo.MsgPriority) {//群点赞消息
        if (msgInfo.MsgPriority == GROUP_MSG_PRIORITY_TYPE.REDPACKET) {
          subType = GROUP_MSG_SUB_TYPE.REDPACKET;
        } else if (msgInfo.MsgPriority == GROUP_MSG_PRIORITY_TYPE.LOVEMSG) {
          subType = GROUP_MSG_SUB_TYPE.LOVEMSG;
        }

      }
      var msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account, subType, fromAccountNick);
      var msgBody = null;
      var msgContent = null;
      var msgType = null;
      for (var mi in msgInfo.MsgBody) {
        msgBody = msgInfo.MsgBody[mi];
        msgType = msgBody.MsgType;
        switch (msgType) {
          case MSG_ELEMENT_TYPE.TEXT:
            msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
            break;
          case MSG_ELEMENT_TYPE.FACE:
            msgContent = new Msg.Elem.Face(
              msgBody.MsgContent.Index,
              msgBody.MsgContent.Data
            );
            break;
          case MSG_ELEMENT_TYPE.IMAGE:
            msgContent = new Msg.Elem.Images(
              msgBody.MsgContent.UUID
            );
            for (var j in msgBody.MsgContent.ImageInfoArray) {
              msgContent.addImage(
                new Msg.Elem.Images.Image(
                  msgBody.MsgContent.ImageInfoArray[j].Type,
                  msgBody.MsgContent.ImageInfoArray[j].Size,
                  msgBody.MsgContent.ImageInfoArray[j].Width,
                  msgBody.MsgContent.ImageInfoArray[j].Height,
                  msgBody.MsgContent.ImageInfoArray[j].URL
                )
              );
            }
            break;
          case MSG_ELEMENT_TYPE.SOUND:
            if (msgBody.MsgContent) {
              msgContent = new Msg.Elem.Sound(
                msgBody.MsgContent.UUID,
                msgBody.MsgContent.Second,
                msgBody.MsgContent.Size,
                msgInfo.From_Account,
                msgInfo.To_Account,
                msgBody.MsgContent.Download_Flag,
                SESSION_TYPE.GROUP
              );
            } else {
              msgType = MSG_ELEMENT_TYPE.TEXT;
              msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
            }
            break;
          case MSG_ELEMENT_TYPE.LOCATION:
            msgContent = new Msg.Elem.Location(
              msgBody.MsgContent.Longitude,
              msgBody.MsgContent.Latitude,
              msgBody.MsgContent.Desc
            );
            break;
          case MSG_ELEMENT_TYPE.FILE:
          case MSG_ELEMENT_TYPE.FILE + " ":
            msgType = MSG_ELEMENT_TYPE.FILE;
            var fileUrl = getFileDownUrl(msgBody.MsgContent.UUID, msgInfo.From_Account, msgBody.MsgContent.FileName);

            if (msgBody.MsgContent) {
              msgContent = new Msg.Elem.File(
                msgBody.MsgContent.UUID,
                msgBody.MsgContent.FileName,
                msgBody.MsgContent.FileSize,
                msgInfo.From_Account,
                msgInfo.To_Account,
                msgBody.MsgContent.Download_Flag,
                SESSION_TYPE.GROUP
              );
            } else {
              msgType = MSG_ELEMENT_TYPE.TEXT;
              msgContent = new Msg.Elem.Text('[文件消息]地址解析出错');
            }
            break;
          case MSG_ELEMENT_TYPE.GROUP_TIP:
            var opType = msgBody.MsgContent.OpType;
            msgContent = new Msg.Elem.GroupTip(
              opType,
              msgBody.MsgContent.Operator_Account,
              group_id,
              msgInfo.GroupInfo.GroupName,
              msgBody.MsgContent.List_Account
            );
            if (GROUP_TIP_TYPE.JOIN == opType || GROUP_TIP_TYPE.QUIT == opType) {//加群或退群时，设置最新群成员数
              msgContent.setGroupMemberNum(msgBody.MsgContent.MemberNum);
            } else if (GROUP_TIP_TYPE.MODIFY_GROUP_INFO == opType) {//群资料变更
              var tempIsCallbackFlag = false;
              var tempNewGroupInfo = {
                "GroupId": group_id,
                "GroupFaceUrl": null,
                "GroupName": null,
                "OwnerAccount": null,
                "GroupNotification": null,
                "GroupIntroduction": null
              };
              var msgGroupNewInfo = msgBody.MsgContent.MsgGroupNewInfo;
              if (msgGroupNewInfo.GroupFaceUrl) {
                var tmpNGIFaceUrl = new Msg.Elem.GroupTip.GroupInfo(
                  GROUP_TIP_MODIFY_GROUP_INFO_TYPE.FACE_URL,
                  msgGroupNewInfo.GroupFaceUrl
                );
                msgContent.addGroupInfo(tmpNGIFaceUrl);
                tempIsCallbackFlag = true;
                tempNewGroupInfo.GroupFaceUrl = msgGroupNewInfo.GroupFaceUrl;
              }
              if (msgGroupNewInfo.GroupName) {
                var tmpNGIName = new Msg.Elem.GroupTip.GroupInfo(
                  GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NAME,
                  msgGroupNewInfo.GroupName
                );
                msgContent.addGroupInfo(tmpNGIName);
                tempIsCallbackFlag = true;
                tempNewGroupInfo.GroupName = msgGroupNewInfo.GroupName;
              }
              if (msgGroupNewInfo.Owner_Account) {
                var tmpNGIOwner = new Msg.Elem.GroupTip.GroupInfo(
                  GROUP_TIP_MODIFY_GROUP_INFO_TYPE.OWNER,
                  msgGroupNewInfo.Owner_Account
                );
                msgContent.addGroupInfo(tmpNGIOwner);
                tempIsCallbackFlag = true;
                tempNewGroupInfo.OwnerAccount = msgGroupNewInfo.Owner_Account;
              }
              if (msgGroupNewInfo.GroupNotification) {
                var tmpNGINotification = new Msg.Elem.GroupTip.GroupInfo(
                  GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NOTIFICATION,
                  msgGroupNewInfo.GroupNotification
                );
                msgContent.addGroupInfo(tmpNGINotification);
                tempIsCallbackFlag = true;
                tempNewGroupInfo.GroupNotification = msgGroupNewInfo.GroupNotification;
              }
              if (msgGroupNewInfo.GroupIntroduction) {
                var tmpNGIIntroduction = new Msg.Elem.GroupTip.GroupInfo(
                  GROUP_TIP_MODIFY_GROUP_INFO_TYPE.INTRODUCTION,
                  msgGroupNewInfo.GroupIntroduction
                );
                msgContent.addGroupInfo(tmpNGIIntroduction);
                tempIsCallbackFlag = true;
                tempNewGroupInfo.GroupIntroduction = msgGroupNewInfo.GroupIntroduction;
              }

              //回调群资料变化通知方法
              if (isSyncGroupMsgs == false && tempIsCallbackFlag && onGroupInfoChangeCallback) {
                onGroupInfoChangeCallback(tempNewGroupInfo);
              }

            } else if (GROUP_TIP_TYPE.MODIFY_MEMBER_INFO == opType) {//群成员变更
              var memberInfos = msgBody.MsgContent.MsgMemberInfo;
              for (var n in memberInfos) {
                var memberInfo = memberInfos[n];
                msgContent.addMemberInfo(
                  new Msg.Elem.GroupTip.MemberInfo(
                    memberInfo.User_Account, memberInfo.ShutupTime
                  )
                );
              }
            }
            break;
          case MSG_ELEMENT_TYPE.CUSTOM:
            msgType = MSG_ELEMENT_TYPE.CUSTOM;
            msgContent = new Msg.Elem.Custom(
              msgBody.MsgContent.Data,
              msgBody.MsgContent.Desc,
              msgBody.MsgContent.Ext
            );
            break;
          default:
            msgType = MSG_ELEMENT_TYPE.TEXT;
            msgContent = new Msg.Elem.Text('web端暂不支持' + msgBody.MsgType + '消息');
            break;
        }
        msg.elems.push(new Msg.Elem(msgType, msgContent));
      }

      if (isAddMsgFlag == false) {//不需要保存消息
        return msg;
      }

      if (MsgStore.addMsg(msg)) {
        return msg;
      } else {
        return null;
      }
    };

    //初始化
    this.init = function (listeners, cbOk, cbErr) {
      if (!listeners.onMsgNotify) {
        log.warn('listeners.onMsgNotify is empty');
      }
      onMsgCallback = listeners.onMsgNotify;

      if (listeners.onBigGroupMsgNotify) {
        onBigGroupMsgCallback = listeners.onBigGroupMsgNotify;
      } else {
        log.warn('listeners.onBigGroupMsgNotify is empty');
      }

      if (listeners.onC2cEventNotifys) {
        onC2cEventCallbacks = listeners.onC2cEventNotifys;
      } else {
        log.warn('listeners.onC2cEventNotifys is empty');
      }
      if (listeners.onGroupSystemNotifys) {
        onGroupSystemNotifyCallbacks = listeners.onGroupSystemNotifys;
      } else {
        log.warn('listeners.onGroupSystemNotifys is empty');
      }
      if (listeners.onGroupInfoChangeNotify) {
        onGroupInfoChangeCallback = listeners.onGroupInfoChangeNotify;
      } else {
        log.warn('listeners.onGroupInfoChangeNotify is empty');
      }
      if (listeners.onFriendSystemNotifys) {
        onFriendSystemNotifyCallbacks = listeners.onFriendSystemNotifys;
      } else {
        log.warn('listeners.onFriendSystemNotifys is empty');
      }
      if (listeners.onProfileSystemNotifys) {
        onProfileSystemNotifyCallbacks = listeners.onProfileSystemNotifys;
      } else {
        log.warn('listeners.onProfileSystemNotifys is empty');
      }
      if (listeners.onKickedEventCall) {
        onKickedEventCall = listeners.onKickedEventCall;
      } else {
        log.warn('listeners.onKickedEventCall is empty');
      }

      if (listeners.onAppliedDownloadUrl) {
        onAppliedDownloadUrl = listeners.onAppliedDownloadUrl;
      } else {
        log.warn('listeners.onAppliedDownloadUrl is empty');
      }

      if (!ctx.identifier || !ctx.userSig) {
        if (cbOk) {
          var success = {
            'ActionStatus': ACTION_STATUS.OK,
            'ErrorCode': 0,
            'ErrorInfo': "login success(no login state)"
          };
          cbOk(success);
        }
        return;
      }

      //初始化
      initMyGroupMaxSeqs(
        function (resp) {
          log.info('initMyGroupMaxSeqs success');
          //初始化文件
          initIpAndAuthkey(
            function (initIpAndAuthkeyResp) {
              log.info('initIpAndAuthkey success');
              if (cbOk) {
                log.info('login success(have login state))');
                var success = {
                  'ActionStatus': ACTION_STATUS.OK,
                  'ErrorCode': 0,
                  'ErrorInfo': "login success"
                };
                cbOk(success);
              }
              MsgManager.setLongPollingOn(true);//开启长轮询
              longPollingOn && MsgManager.longPolling(cbOk);
            }, cbErr);
        }, cbErr);
    };

    //发消息（私聊或群聊）
    this.sendMsg = function (msg, cbOk, cbErr) {
      proto_sendMsg(msg, function (resp) {
        //私聊时，加入自己的发的消息，群聊时，由于seq和服务器的seq不一样，所以不作处理
        if (msg.sess.type() == SESSION_TYPE.C2C) {
          if (!MsgStore.addMsg(msg)) {
            var errInfo = "sendMsg: addMsg failed!";
            var error = tool.getReturnError(errInfo, -17);
            log.error(errInfo);
            if (cbErr) cbErr(error);
            return;
          }
          //更新信息流时间
          MsgStore.updateTimeline();
        }
        if (cbOk) cbOk(resp);
      }, function (err) {
        if (cbErr) cbErr(err);
      });
    };
  };

  //上传文件
  var FileUploader = new function () {
    this.fileMd5 = null;
    //获取文件MD5
    var getFileMD5 = function (file, cbOk, cbErr) {

      //FileReader pc浏览器兼容性
      //Feature   Firefox (Gecko) Chrome  Internet Explorer   Opera   Safari
      //Basic support 3.6 7   10                      12.02   6.0.2
      var fileReader = null;
      try {
        fileReader = new FileReader();//分块读取文件对象
      } catch (e) {
        if (cbErr) {
          cbErr(tool.getReturnError('当前浏览器不支持FileReader', -18));
          return;
        }
      }
      //file的slice方法，注意它的兼容性，在不同浏览器的写法不同
      var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;
      if (!blobSlice) {
        if (cbErr) {
          cbErr(tool.getReturnError('当前浏览器不支持FileAPI', -19));
          return;
        }
      }

      var chunkSize = 2 * 1024 * 1024;//分块大小，2M
      var chunks = Math.ceil(file.size / chunkSize);//总块数
      var currentChunk = 0;//当前块数
      var spark = new SparkMD5();//获取MD5对象

      fileReader.onload = function (e) {//数据加载完毕事件

        var binaryStr = "";
        var bytes = new Uint8Array(e.target.result);
        var length = bytes.byteLength;
        for (var i = 0; i < length; i++) {
          binaryStr += String.fromCharCode(bytes[i]);//二进制转换字符串
        }
        spark.appendBinary(binaryStr);
        currentChunk++;
        if (currentChunk < chunks) {
          loadNext();//读取下一块数据
        } else {
          this.fileMd5 = spark.end();//得到文件MD5值
          if (cbOk) {
            cbOk(this.fileMd5);
          }
        }
      };
      //分片读取文件
      function loadNext() {
        var start = currentChunk * chunkSize, end = start + chunkSize >= file.size ? file.size : start + chunkSize;
        //根据开始和结束位置，切割文件
        var b = blobSlice.call(file, start, end);
        //readAsBinaryString ie浏览器不兼容此方法
        //fileReader.readAsBinaryString(blobSlice.call(file, start, end));
        fileReader.readAsArrayBuffer(b);//ie，chrome，firefox等主流浏览器兼容此方法

      }

      loadNext();//开始读取
    };
    //提交上传图片表单(用于低版本IE9以下)
    this.submitUploadFileForm = function (options, cbOk, cbErr) {
      var errInfo;
      var error;
      var formId = options.formId;
      var fileId = options.fileId;
      var iframeNum = uploadResultIframeId++;
      var iframeName = "uploadResultIframe_" + iframeNum;
      var toAccount = options.To_Account;
      var businessType = options.businessType;

      var form = document.getElementById(formId);
      if (!form) {
        errInfo = "获取表单对象为空: formId=" + formId + "(formId非法)";
        error = tool.getReturnError(errInfo, -20);
        if (cbErr) cbErr(error);
        return;
      }

      var fileObj = document.getElementById(fileId);
      if (!fileObj) {
        errInfo = "获取文件对象为空: fileId=" + fileId + "(没有选择文件或者fileId非法)";
        error = tool.getReturnError(errInfo, -21);
        if (cbErr) cbErr(error);
        return;
      }
      //fileObj.type="file";//ie8下不起作用，必须由业务自己设置
      fileObj.name = "file";

      var iframe = document.createElement("iframe");
      iframe.name = iframeName;
      iframe.id = iframeName;
      iframe.style.display = "none";
      document.body.appendChild(iframe);

      var cmdName;
      if (isAccessFormalEnv()) {
        cmdName = 'pic_up';
      } else {
        cmdName = 'pic_up_test';
      }
      var uploadApiUrl = "https://pic.tim.qq.com/v4/openpic/" + cmdName + "?tinyid=" + ctx.tinyid + "&a2=" + ctx.a2 + "&sdkappid=" + ctx.sdkAppID + "&accounttype=" + ctx.accountType + "&contenttype=http";
      form.action = uploadApiUrl;
      form.method = 'post';
      //form.enctype='multipart/form-data';//ie8下不起作用，必须由业务自己设置
      form.target = iframeName;

      function createFormInput(name, value) {
        var tempInput = document.createElement("input");
        tempInput.type = "hidden";
        tempInput.name = name;
        tempInput.value = value;
        form.appendChild(tempInput);
      }

      createFormInput("App_Version", VERSION_INFO.APP_VERSION);
      createFormInput("From_Account", ctx.identifier);
      createFormInput("To_Account", toAccount);
      createFormInput("Seq", nextSeq().toString());
      createFormInput("Timestamp", unixtime().toString());
      createFormInput("Random", createRandom().toString());
      createFormInput("Busi_Id", businessType);
      createFormInput("PkgFlag", UPLOAD_RES_PKG_FLAG.RAW_DATA.toString());
      createFormInput("Auth_Key", authkey);
      createFormInput("Server_Ver", VERSION_INFO.SERVER_VERSION.toString());
      createFormInput("File_Type", options.fileType);


      //检测iframe.contentWindow.name是否有值
      function checkFrameName() {
        var resp;
        try {
          resp = JSON.parse(iframe.contentWindow.name) || {};
        } catch (e) {
          resp = {};
        }
        if (resp.ActionStatus) {//上传接口返回
          // We've got what we need. Stop the iframe from loading further content.
          iframe.src = "about:blank";
          iframe.parentNode.removeChild(iframe);
          iframe = null;

          if (resp.ActionStatus == ACTION_STATUS.OK) {
            cbOk && cbOk(resp);
          } else {
            cbErr && cbErr(resp);
          }
        } else {
          setTimeout(checkFrameName, 100);
        }
      }

      setTimeout(checkFrameName, 500);

      form.submit();//提交上传图片表单
    };
    //上传图片或文件(用于高版本浏览器，支持FileAPI)
    this.uploadFile = function (options, cbOk, cbErr) {

      var file_upload = {
        //初始化
        init: function (options, cbOk, cbErr) {
          var me = this;
          me.file = options.file;
          //分片上传进度回调事件
          me.onProgressCallBack = options.onProgressCallBack;
          //停止上传图片按钮
          if (options.abortButton) {
            options.abortButton.onclick = me.abortHandler;
          }
          me.total = me.file.size;//文件总大小
          me.loaded = 0;//已读取字节数
          me.step = 1080 * 1024;//分块大小，1080K
          me.sliceSize = 0;//分片大小
          me.sliceOffset = 0;//当前分片位置
          me.timestamp = unixtime();//当前时间戳
          me.seq = nextSeq();//请求seq
          me.random = createRandom();//请求随机数
          me.fromAccount = ctx.identifier;//发送者
          me.toAccount = options.To_Account;//接收者
          me.fileMd5 = options.fileMd5;//文件MD5
          me.businessType = options.businessType;//图片或文件的业务类型，群消息:1; c2c消息:2; 个人头像：3; 群头像：4;
          me.fileType = options.fileType;//文件类型，不填为默认认为上传的是图片；1：图片；2：文件；3：短视频；4：PTT

          me.cbOk = cbOk;//上传成功回调事件
          me.cbErr = cbErr;//上传失败回调事件

          me.reader = new FileReader();//读取文件对象
          me.blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;//file的slice方法,不同浏览器不一样

          me.reader.onloadstart = me.onLoadStart;//开始读取回调事件
          me.reader.onprogress = me.onProgress;//读取文件进度回调事件
          me.reader.onabort = me.onAbort;//停止读取回调事件
          me.reader.onerror = me.onerror;//读取发生错误回调事件
          me.reader.onload = me.onLoad;//分片加载完毕回调事件
          me.reader.onloadend = me.onLoadEnd;//读取文件完毕回调事件
        },
        //上传方法
        upload: function () {
          var me = file_upload;
          //读取第一块
          me.readBlob(0);
        },
        onLoadStart: function () {
          var me = file_upload;
        },
        onProgress: function (e) {
          var me = file_upload;
          me.loaded += e.loaded;
          if (me.onProgressCallBack) {
            me.onProgressCallBack(me.loaded, me.total);
          }
        },
        onAbort: function () {
          var me = file_upload;
        },
        onError: function () {
          var me = file_upload;
        },
        onLoad: function (e) {
          var me = file_upload;
          if (e.target.readyState == FileReader.DONE) {
            var slice_data_base64 = e.target.result;
            //注意，一定要去除base64编码头部
            var pos = slice_data_base64.indexOf(",");
            if (pos != -1) {
              slice_data_base64 = slice_data_base64.substr(pos + 1);
            }
            //封装上传图片接口的请求参数
            var opt = {
              'From_Account': me.fromAccount,
              'To_Account': me.toAccount,
              'Busi_Id': me.businessType,
              'File_Type': me.fileType,
              'File_Str_Md5': me.fileMd5,
              'PkgFlag': UPLOAD_RES_PKG_FLAG.BASE64_DATA,
              'File_Size': me.total,
              'Slice_Offset': me.sliceOffset,
              'Slice_Size': me.sliceSize,
              'Slice_Data': slice_data_base64,
              'Seq': me.seq,
              'Timestamp': me.timestamp,
              'Random': me.random
            };

            //上传成功的成功回调
            var succCallback = function (resp) {
              if (resp.IsFinish == 0) {
                me.loaded = resp.Next_Offset;
                if (me.loaded < me.total) {
                  me.readBlob(me.loaded);
                } else {
                  me.loaded = me.total;
                }
              } else {

                if (me.cbOk) {
                  var tempResp = {
                    'ActionStatus': resp.ActionStatus,
                    'ErrorCode': resp.ErrorCode,
                    'ErrorInfo': resp.ErrorInfo,
                    'File_UUID': resp.File_UUID,
                    'File_Size': resp.Next_Offset,
                    'URL_INFO': resp.URL_INFO,
                    'Download_Flag': resp.Download_Flag
                  };
                  if (me.fileType == UPLOAD_RES_TYPE.FILE) {//如果上传的是文件，下载地址需要sdk内部拼接
                    tempResp.URL_INFO = getFileDownUrl(resp.File_UUID, ctx.identifier, me.file.name);
                  }
                  me.cbOk(tempResp);
                }
              }
              Upload_Retry_Times = 0;
            };
            //上传失败的回调
            var errorCallback = function (resp) {
              if (Upload_Retry_Times < Upload_Retry_Max_Times) {
                Upload_Retry_Times++;
                setTimeout(function () {
                  proto_uploadPic(opt, succCallback, errorCallback);
                }, 1000);
              } else {
                me.cbErr(resp);
              }
              //me.cbErr
            };
            //分片上传图片接口
            proto_uploadPic(opt, succCallback, errorCallback);
          }
        },
        onLoadEnd: function () {
          var me = file_upload;
        },
        //分片读取文件方法
        readBlob: function (start) {
          var me = file_upload;
          var blob, file = me.file;
          var end = start + me.step;
          if (end > me.total) {
            end = me.total;
            me.sliceSize = end - start;
          } else {
            me.sliceSize = me.step;
          }
          me.sliceOffset = start;
          //根据起始和结束位置，分片读取文件
          blob = me.blobSlice.call(file, start, end);
          //将分片的二进制数据转换为base64编码
          me.reader.readAsDataURL(blob);
        },
        abortHandler: function () {
          var me = file_upload;
          if (me.reader) {
            me.reader.abort();
          }
        }
      };

      //读取文件MD5
      getFileMD5(options.file,
        function (fileMd5) {
          log.info('fileMd5: ' + fileMd5);
          options.fileMd5 = fileMd5;
          //初始化上传参数
          file_upload.init(options, cbOk, cbErr);
          //开始上传文件
          file_upload.upload();
        },
        cbErr
      );
    };
  };


  //web im 基础对象

  //常量对象

  //会话类型
  webim.SESSION_TYPE = SESSION_TYPE;

  webim.MSG_MAX_LENGTH = MSG_MAX_LENGTH;

  //c2c消息子类型
  webim.C2C_MSG_SUB_TYPE = C2C_MSG_SUB_TYPE;

  //群消息子类型
  webim.GROUP_MSG_SUB_TYPE = GROUP_MSG_SUB_TYPE;

  //消息元素类型
  webim.MSG_ELEMENT_TYPE = MSG_ELEMENT_TYPE;

  //群提示消息类型
  webim.GROUP_TIP_TYPE = GROUP_TIP_TYPE;

  //图片类型
  webim.IMAGE_TYPE = IMAGE_TYPE;

  //群系统消息类型
  webim.GROUP_SYSTEM_TYPE = GROUP_SYSTEM_TYPE;

  //好友系统通知子类型
  webim.FRIEND_NOTICE_TYPE = FRIEND_NOTICE_TYPE;

  //群提示消息-群资料变更类型
  webim.GROUP_TIP_MODIFY_GROUP_INFO_TYPE = GROUP_TIP_MODIFY_GROUP_INFO_TYPE;

  //浏览器信息
  webim.BROWSER_INFO = BROWSER_INFO;

  //表情对象
  webim.Emotions = webim.EmotionPicData = emotions;
  //表情标识符和index Map
  webim.EmotionDataIndexs = webim.EmotionPicDataIndex = emotionDataIndexs;

  //腾讯登录服务错误码(托管模式)
  webim.TLS_ERROR_CODE = TLS_ERROR_CODE;

  //连接状态
  webim.CONNECTION_STATUS = CONNECTION_STATUS;

  //上传图片业务类型
  webim.UPLOAD_PIC_BUSSINESS_TYPE = UPLOAD_PIC_BUSSINESS_TYPE;

  //最近联系人类型
  webim.RECENT_CONTACT_TYPE = RECENT_CONTACT_TYPE;

  //上传资源类型
  webim.UPLOAD_RES_TYPE = UPLOAD_RES_TYPE;


  /**************************************/

  //类对象
  //
  //工具对象
  webim.Tool = tool;
  //控制台打印日志对象
  webim.Log = log;

  //消息对象
  webim.Msg = Msg;
  //会话对象
  webim.Session = Session;
  //会话存储对象
  webim.MsgStore = {
    sessMap: function () {
      return MsgStore.sessMap();
    },
    sessCount: function () {
      return MsgStore.sessCount();
    },
    sessByTypeId: function (type, id) {
      return MsgStore.sessByTypeId(type, id);
    },
    delSessByTypeId: function (type, id) {
      return MsgStore.delSessByTypeId(type, id);
    },
    resetCookieAndSyncFlag: function () {
      return MsgStore.resetCookieAndSyncFlag();
    }
  };

  webim.Resources = Resources;

  /**************************************/

  // webim API impl
  //
  //基本接口
  //登录
  webim.login = webim.init = function (loginInfo, listeners, opts, cbOk, cbErr) {

    //初始化连接状态回调函数
    ConnManager.init(listeners.onConnNotify, cbOk, cbErr);

    //设置ie9以下浏览器jsonp回调
    if (listeners.jsonpCallback) jsonpCallback = listeners.jsonpCallback;
    //登录
    _login(loginInfo, listeners, opts, cbOk, cbErr);
  };
  //登出
  //需要传长轮询id
  //这样登出之后其他的登录实例还可以继续收取消息
  webim.logout = webim.offline = function (cbOk, cbErr) {
    return proto_logout('instance', cbOk, cbErr);
  };

  //登出
  //这种登出方式，所有的实例都将不会收到消息推送，直到重新登录
  webim.logoutAll = function (cbOk, cbErr) {
    return proto_logout('all', cbOk, cbErr);
  };


  //消息管理接口
  //发消息接口（私聊和群聊）
  webim.sendMsg = function (msg, cbOk, cbErr) {
    return MsgManager.sendMsg(msg, cbOk, cbErr);
  };
  //拉取未读c2c消息
  webim.syncMsgs = function (cbOk, cbErr) {
    return MsgManager.syncMsgs(cbOk, cbErr);
  };
  //拉取C2C漫游消息
  webim.getC2CHistoryMsgs = function (options, cbOk, cbErr) {
    return MsgManager.getC2CHistoryMsgs(options, cbOk, cbErr);
  };
  //拉取群漫游消息
  webim.syncGroupMsgs = function (options, cbOk, cbErr) {
    return MsgManager.syncGroupMsgs(options, cbOk, cbErr);
  };

  //上报c2c消息已读
  webim.c2CMsgReaded = function (options, cbOk, cbErr) {
    return MsgStore.c2CMsgReaded(options, cbOk, cbErr);
  };

  //上报群消息已读
  webim.groupMsgReaded = function (options, cbOk, cbErr) {
    return proto_groupMsgReaded(options, cbOk, cbErr);
  };

  //设置聊天会话自动标记已读
  webim.setAutoRead = function (selSess, isOn, isResetAll) {
    return MsgStore.setAutoRead(selSess, isOn, isResetAll);
  };

  //群组管理接口
  //
  //创建群
  webim.createGroup = function (options, cbOk, cbErr) {
    return proto_createGroup(options, cbOk, cbErr);
  };
  //创建群-高级接口
  webim.createGroupHigh = function (options, cbOk, cbErr) {
    return proto_createGroupHigh(options, cbOk, cbErr);
  };
  //申请加群
  webim.applyJoinGroup = function (options, cbOk, cbErr) {
    return proto_applyJoinGroup(options, cbOk, cbErr);
  };
  //处理加群申请(同意或拒绝)
  webim.handleApplyJoinGroupPendency = function (options, cbOk, cbErr) {
    return proto_handleApplyJoinGroupPendency(options, cbOk, cbErr);
  };

  //删除加群申请
  webim.deleteApplyJoinGroupPendency = function (options, cbOk, cbErr) {
    return proto_deleteC2CMsg(options, cbOk, cbErr);
  };

  //主动退群
  webim.quitGroup = function (options, cbOk, cbErr) {
    return proto_quitGroup(options, cbOk, cbErr);
  };
  //搜索群组(根据名称)
  webim.searchGroupByName = function (options, cbOk, cbErr) {
    return proto_searchGroupByName(options, cbOk, cbErr);
  };
  //获取群组公开资料(根据群id搜索)
  webim.getGroupPublicInfo = function (options, cbOk, cbErr) {
    return proto_getGroupPublicInfo(options, cbOk, cbErr);
  };
  //获取群组详细资料-高级接口
  webim.getGroupInfo = function (options, cbOk, cbErr) {
    return proto_getGroupInfo(options, cbOk, cbErr);
  };
  //修改群基本资料
  webim.modifyGroupBaseInfo = function (options, cbOk, cbErr) {
    return proto_modifyGroupBaseInfo(options, cbOk, cbErr);
  };
  //获取群成员列表
  webim.getGroupMemberInfo = function (options, cbOk, cbErr) {
    return proto_getGroupMemberInfo(options, cbOk, cbErr);
  };
  //邀请好友加群
  webim.addGroupMember = function (options, cbOk, cbErr) {
    return proto_addGroupMember(options, cbOk, cbErr);
  };
  //修改群成员资料
  webim.modifyGroupMember = function (options, cbOk, cbErr) {
    return proto_modifyGroupMember(options, cbOk, cbErr);
  };
  //删除群成员
  webim.deleteGroupMember = function (options, cbOk, cbErr) {
    return proto_deleteGroupMember(options, cbOk, cbErr);
  };
  //解散群
  webim.destroyGroup = function (options, cbOk, cbErr) {
    return proto_destroyGroup(options, cbOk, cbErr);
  };
  //转让群组
  webim.changeGroupOwner = function (options, cbOk, cbErr) {
    return proto_changeGroupOwner(options, cbOk, cbErr);
  };

  //获取我的群组列表-高级接口
  webim.getJoinedGroupListHigh = function (options, cbOk, cbErr) {
    return proto_getJoinedGroupListHigh(options, cbOk, cbErr);
  };
  //获取群成员角色
  webim.getRoleInGroup = function (options, cbOk, cbErr) {
    return proto_getRoleInGroup(options, cbOk, cbErr);
  };
  //设置群成员禁言时间
  webim.forbidSendMsg = function (options, cbOk, cbErr) {
    return proto_forbidSendMsg(options, cbOk, cbErr);
  };
  //发送自定义群系统通知
  webim.sendCustomGroupNotify = function (options, cbOk, cbErr) {
    return proto_sendCustomGroupNotify(options, cbOk, cbErr);
  };

  //进入大群
  webim.applyJoinBigGroup = function (options, cbOk, cbErr) {
    return proto_applyJoinBigGroup(options, cbOk, cbErr);
  };
  //退出大群
  webim.quitBigGroup = function (options, cbOk, cbErr) {
    return proto_quitBigGroup(options, cbOk, cbErr);
  };

  //资料关系链管理接口
  //
  //获取个人资料接口，可用于搜索用户
  webim.getProfilePortrait = function (options, cbOk, cbErr) {
    return proto_getProfilePortrait(options, cbOk, cbErr);
  };
  //设置个人资料
  webim.setProfilePortrait = function (options, cbOk, cbErr) {
    return proto_setProfilePortrait(options, cbOk, cbErr);
  };
  //申请加好友
  webim.applyAddFriend = function (options, cbOk, cbErr) {
    return proto_applyAddFriend(options, cbOk, cbErr);
  };
  //获取好友申请列表
  webim.getPendency = function (options, cbOk, cbErr) {
    return proto_getPendency(options, cbOk, cbErr);
  };
  //删除好友申请
  webim.deletePendency = function (options, cbOk, cbErr) {
    return proto_deletePendency(options, cbOk, cbErr);
  };
  //处理好友申请
  webim.responseFriend = function (options, cbOk, cbErr) {
    return proto_responseFriend(options, cbOk, cbErr);
  };
  //获取我的好友
  webim.getAllFriend = function (options, cbOk, cbErr) {
    return proto_getAllFriend(options, cbOk, cbErr);
  };
  //删除好友
  webim.deleteFriend = function (options, cbOk, cbErr) {
    return proto_deleteFriend(options, cbOk, cbErr);
  };
  //拉黑
  webim.addBlackList = function (options, cbOk, cbErr) {
    return proto_addBlackList(options, cbOk, cbErr);
  };
  //删除黑名单
  webim.deleteBlackList = function (options, cbOk, cbErr) {
    return proto_deleteBlackList(options, cbOk, cbErr);
  };
  //获取我的黑名单
  webim.getBlackList = function (options, cbOk, cbErr) {
    return proto_getBlackList(options, cbOk, cbErr);
  };

  //获取最近会话
  webim.getRecentContactList = function (options, cbOk, cbErr) {
    return proto_getRecentContactList(options, cbOk, cbErr);
  };

  //图片或文件服务接口
  //
  //上传文件接口（高版本浏览器）
  webim.uploadFile = webim.uploadPic = function (options, cbOk, cbErr) {
    return FileUploader.uploadFile(options, cbOk, cbErr);
  };
  //提交上传图片表单接口（用于低版本ie）
  webim.submitUploadFileForm = function (options, cbOk, cbErr) {
    return FileUploader.submitUploadFileForm(options, cbOk, cbErr);
  };
  //上传图片或文件(Base64)接口
  webim.uploadFileByBase64 = webim.uploadPicByBase64 = function (options, cbOk, cbErr) {
    //请求参数
    var opt = {
      'To_Account': options.toAccount,
      'Busi_Id': options.businessType,
      'File_Type': options.File_Type,
      'File_Str_Md5': options.fileMd5,
      'PkgFlag': UPLOAD_RES_PKG_FLAG.BASE64_DATA,
      'File_Size': options.totalSize,
      'Slice_Offset': 0,
      'Slice_Size': options.totalSize,
      'Slice_Data': options.base64Str,
      'Seq': nextSeq(),
      'Timestamp': unixtime(),
      'Random': createRandom()
    };
    return proto_uploadPic(opt, cbOk, cbErr);
  };

  //设置jsonp返回的值
  webim.setJsonpLastRspData = function (rspData) {
    jsonpLastRspData = typeof (rspData) == "string" ? JSON.parse(rspData) : rspData;
  };

  //获取长轮询ID
  webim.getLongPollingId = function (options, cbOk, cbErr) {
    return proto_getLongPollingId(options, cbOk, cbErr);
  };

  //获取下载地址
  webim.applyDownload = function (options, cbOk, cbErr) {
    return proto_applyDownload(options, cbOk, cbErr);
  };

  //获取下载地址
  webim.onDownFile = function (uuid) {
    window.open(Resources.downloadMap["uuid_" + uuid]);
  };

  //检查是否登录
  webim.checkLogin = function (cbErr, isNeedCallBack) {
    return checkLogin(cbErr, isNeedCallBack);
  };
})(webim);



// 暴露对应接口
module.exports = webim;